library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.2 ──✔ ggplot2 3.4.0      ✔ purrr   0.3.5 
✔ tibble  3.1.8      ✔ dplyr   1.0.10
✔ tidyr   1.2.1      ✔ stringr 1.4.1 
✔ readr   2.1.3      ✔ forcats 0.5.2 ── Conflicts ───────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(janitor)

Attaching package: ‘janitor’

The following objects are masked from ‘package:stats’:

    chisq.test, fisher.test
library(lubridate)

Attaching package: ‘lubridate’

The following objects are masked from ‘package:base’:

    date, intersect, setdiff, union
library(GGally)
Registered S3 method overwritten by 'GGally':
  method from   
  +.gg   ggplot2
library(ggfortify)
library(modelr)
library(tidytext)
library(wordcloud)
Loading required package: RColorBrewer
library(leaflet)
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
library(ggridges)
library(ggthemes)
library(relaimpo)
Loading required package: MASS

Attaching package: ‘MASS’

The following object is masked from ‘package:dplyr’:

    select

Loading required package: boot
Loading required package: survey
Loading required package: grid
Loading required package: Matrix

Attaching package: ‘Matrix’

The following objects are masked from ‘package:tidyr’:

    expand, pack, unpack

Loading required package: survival

Attaching package: ‘survival’

The following object is masked from ‘package:boot’:

    aml


Attaching package: ‘survey’

The following object is masked from ‘package:graphics’:

    dotchart

Loading required package: mitools
This is the global version of package relaimpo.

If you are a non-US user, a version with the interesting additional metric pmvd is available

from Ulrike Groempings web site at prof.beuth-hochschule.de/groemping.
library(tm)
Loading required package: NLP

Attaching package: ‘NLP’

The following object is masked from ‘package:ggplot2’:

    annotate
library(SnowballC)
library(wordcloud)
library(RColorBrewer)

Introduction

About the Data

The data is about New York City Airbnb listings in 2019.

The data includes information on, prices, New York neighbourhoods and room types, to name a few. Geospatial coordinates are also present, offering valuable insight into Airbnb locations.

Project Objectives

Primary aim: - Analyse the determinants of airbnb prices - Offer recommendations for customers booking airbnbs - Provide insights for hosts - adopt an appropriate pricing strategy

Overview

  • Ethical Considerations
  • Data Cleaning and Wrangling
  • Exploratory Analysis
  • Text mining
  • Geo-spatial Analysis
  • Model Building
    • Univariate Regression
    • Multivariate Regression
  • Analysis Conclusions
prices <- read_csv("raw_data/AB_NYC_2019.csv")
Rows: 48895 Columns: 16── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr   (5): name, host_name, neighbourhood_group, neighbourhood, room_type
dbl  (10): id, host_id, latitude, longitude, price, minimum_nights, number_of_reviews, reviews_per_month, calculate...
date  (1): last_review
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
prices %>% 
  summarise(n = mean(reviews_per_month, na.rm = TRUE))

Ethical Considerations

The data contains information on host names and unique IDs. To avoid any ethical issues I chose to remove these variables.

Clean Data

Check Missing Values

# check for missing values
prices %>% 
  summarise(
    across(
      .cols = everything(),
      .fns = ~sum(is.na(.x))
    )
  ) %>% 
  dplyr::select(c(name, host_name, last_review, reviews_per_month))

Many missing values in last_review and reviews_per_month (10052 rows)

  • last_review will be dropped as inconsequential variable
  • values dropped from reviews_per_month
    • coalescing with mean may warp data too much
prices_df <- prices %>% 
  # drop host names and host_id (ethical)
  dplyr::select(-c(host_name, host_id, id)) %>% 
  # take out month from last_review 
  mutate(last_review_month = month(last_review, label = TRUE),
         # take out year from last_review
         last_review_year = year(last_review),
         # take name length
         name_length = str_length(name)) %>% 
  # drop NA values
  drop_na() %>% 
  dplyr::select(-c(last_review, last_review_year))

Exploratory Analysis

Total Number of Bookings per Borough

# number of bookings per borough
prices_df %>% 
  group_by(neighbourhood_group) %>% 
  summarise(num_bookings = n()) %>% 
  arrange(desc(num_bookings)) %>% 
  ggplot(aes(reorder(neighbourhood_group, num_bookings), num_bookings, 
             fill = neighbourhood_group)) +
  geom_col(show.legend = FALSE) +
  geom_label(mapping = aes(label = num_bookings), size = 3, 
             fill = "#F5FFFA", fontface = "bold", hjust = 0.5) +
  theme_classic() +
  labs(x = "Borough", y = "Number of Bookings", 
       title = "Total Number of Bookings per Borough")

Manhattan and Brooklyn both by far the most popular areas for Airbnb listings.

Two central Boroughs which may indicate the main reason people book is for holidays / Tourism.

Average Price per Neighbourhood Group


# Average Price per Room
price_per_room <- prices_df %>% 
  group_by(room_type) %>% 
  summarise(avg_price = mean(price)) %>% 
  ggplot(aes(room_type, avg_price, fill = room_type)) +
  geom_col(show.legend = FALSE) +
  geom_label(mapping = aes(label = round(avg_price, 2)), size = 6, 
             fill = "#F5FFFA", fontface = "bold", 
             position = position_stack(vjust = 0.9)) +
  theme_classic() +
  labs(x = "Room Type", y = "Average Price ($)", title = "Average Price by Room Type")


# Average price per neighbourhood group
price_room_borough <- prices_df %>% 
  group_by(neighbourhood_group, room_type) %>% 
  summarise(avg_price = mean(price)) %>% 
  arrange(desc(avg_price)) %>% 
  ggplot(aes(reorder(neighbourhood_group, avg_price), avg_price, 
             fill = room_type)) +
  geom_col() + 
  geom_label(mapping = aes(label = round(avg_price, 2)), size = 2.5, 
             fill = "#F5FFFA", fontface = "bold", hjust = 0.5, 
             position = position_stack(vjust = 0.9)) +
  theme_classic() +
  labs(x = "New York Boroughs", y = "Average Price ($)", 
       title = "Average Price per Borough by Room Type") + 
  facet_wrap(~room_type) +
  coord_flip()
`summarise()` has grouped output by 'neighbourhood_group'. You can override using the `.groups` argument.
cowplot::plot_grid(price_per_room, price_room_borough, nrow = 2)

The 10 most expensive and cheapest New York Districts

# top 10 most expensive districts on average
a <- prices_df %>% 
  group_by(neighbourhood) %>% 
  summarise(avg_price = mean(price)) %>% 
  arrange(desc(avg_price)) %>% 
  slice(1:10) %>% 
  ggplot(aes(reorder(neighbourhood, avg_price), avg_price, fill = neighbourhood)) + 
  geom_col(show.legend = FALSE) +
  geom_label(mapping = aes(label = round(avg_price, 2)), size = 3, 
             fill = "#F5FFFA", fontface = "bold") +
  # coord_flip() +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 30, vjust = 0.95, hjust = 1)) +
  labs(x = "Neighbourhood", y = "Average Price ($)", 
       title = "The 10 Most Expensive Districts on Average")

# top 10 least expensive districts on average
b <- prices_df %>% 
  group_by(neighbourhood) %>% 
  summarise(avg_price = mean(price)) %>% 
  arrange(avg_price) %>% 
  slice(1:10) %>% 
  ggplot(aes(reorder(neighbourhood, avg_price), avg_price, fill = neighbourhood)) + 
  geom_col(show.legend = FALSE) +
  geom_label(mapping = aes(label = round(avg_price, 2)), size = 3, 
             fill = "#F5FFFA", fontface = "bold") +
  # coord_flip() +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 30, vjust = 0.95, hjust = 1)) +
  labs(x = "Neighbourhood", y = "Average Price ($)", 
       title = "The 10 Least Expensive Districts on Average")

cowplot::plot_grid(a, b, nrow=2)

The 10 most expensive districts are all located in Manhattan apart from, Neponsit (Queens) and WillowBrook (Staten Island)

The majority of the 10 least expensive districts reside in the Bronx, State Island and Queens

Average Reviews by Last Month Review Submitted

# Average Reviews per Month by Last Month review was left
prices_df %>% 
  mutate(last_review_month = as_factor(last_review_month)) %>% 
  group_by(last_review_month) %>% 
  summarise(n = mean(reviews_per_month, na.rm = FALSE)) %>% 
  ggplot(aes(last_review_month, n, fill = last_review_month)) + 
  geom_col(show.legend = FALSE) +
  theme_bw() +
  labs(x = "Last Review Month", y = "Average Reviews per Month", 
       title = "Average Reviews by Last Month Review Submitted")

Suggests that most people are leaving reviews in the summer, indicating some seasonality to Airbnb booking in New York.

Price Density by Area

ggplot(subset(prices_df, price < 500),aes(x = price)) +
  geom_density(
    mapping = aes(fill = neighbourhood_group), 
    bandwidth = 100, 
    alpha = 1, size = 0.5, show.legend = FALSE) +
  theme_bw() +
  # scale_fill_economist() +
  facet_wrap(~neighbourhood_group) +
  labs(x = "Price", y = "Density", title = "Price Density by Borough")
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
Please use `linewidth` instead.Warning: Ignoring unknown parameters: `bandwidth`

Pricing density plot reveals that boroughs with fewer amount of bookings (Queens, Staten Island and Bronx) have a higher density of lower prices

Most common areas (Mahattan and Brooklyn) have a wider density plot indicating that prices vary more.

Text Mining

I want to find the words that are associated with different price ranges.

So I need to create new variables which classify the price range of each airbnb

we will define price ranges based around the average price for total bookings

prices_df %>% 
  summarise(mean_price = mean(price))

mean price is $142 therefore, low will be less than $100, medium will be between $100 - $200, high will be $200 - $300 and very high will be greater than $300

prices_new <- prices_df %>% 
  mutate(price_class = case_when(
    price < 100 ~ "Low", 
    price < 200 ~ "Medium", 
    price < 300 ~ "High",
    TRUE ~ "Very High"
  ))
  

High/Very High Price Range

# words associated with high prices
high_price_words <- prices_new %>% 
  # filter for High and Very High price classes 
  filter(price_class %in% c("High", "Very High")) %>% 
  # take individual words from the name column
  unnest_tokens(word, name) %>% 
  # take out stop_words 
  anti_join(stop_words) %>% 
  # select word column
  dplyr::select(word)
Joining, by = "word"
sorted_hp_words <- high_price_words %>% 
  count(word, sort = TRUE)

wordcloud(words = sorted_hp_words$word, freq = sorted_hp_words$n, min.freq = 1, 
          max.words=60, random.order=FALSE, rot.per=0.35, 
          colors=brewer.pal(8, "Spectral"))

Words that stand out from wordcloud include: bedroom, apartment, village, luxury, location, Manhattan, spacious, park - start to understand what kind of Airbnbs are being advertised for high prices

Low Price Range

# words associated with low prices
low_price_words <- prices_new %>% 
  filter(price_class %in% "Low") %>% 
  unnest_tokens(word, name) %>% 
  anti_join(stop_words) %>% 
  dplyr::select(word)
Joining, by = "word"
sorted_lp_words <- low_price_words %>% 
  count(word, sort = TRUE)

wordcloud(words = sorted_lp_words$word, freq = sorted_lp_words$n, min.freq = 1, max.words=60, 
          random.order=FALSE, rot.per=0.35, colors=brewer.pal(8, "Spectral"))

when we look at the word cloud of the low price range, we see some similarities with the high price range indicating owners are trying to sell the property as up market.

Big emphasis on property being “private” which is likely to be a big concern for people when not paying that much. In contrast, privacy is a given when paying for high end accommodation.

Medium Price Range

# words associated with medium price range (around the average)
medium_price_words <- prices_new %>% 
  filter(price_class %in% "Medium") %>% 
  unnest_tokens(word, name) %>% 
  anti_join(stop_words) %>% 
  dplyr::select(word)
Joining, by = "word"
sorted_mp_words <- medium_price_words %>% 
  count(word, sort = TRUE)

wordcloud(words = sorted_mp_words$word, freq = sorted_mp_words$n, min.freq = 1, max.words=60, 
          random.order=FALSE, rot.per=0.35, colors=brewer.pal(8, "Spectral"))

Not a tremendous amount of difference here, probably as expected it takes a balance between low and high price ranges highlighting privacy as important but also more emphasis on location.

Geo-spatial Analysis

Can we see if the geo-spatial backs up the word cloud and bigram analysis of emphasis on location for medium and low price ranges

Density of New York Boroughs

# Density of New York Boroughs
prices_new %>% 
  ggplot(aes(longitude, latitude)) +
  geom_density2d(aes(colour = neighbourhood_group)) + 
  theme_bw() +
  labs(title = "Density of Boroughs")

Density of Price Class

# Density of Price Class
prices_new %>% 
  ggplot(aes(longitude, latitude)) +
  geom_density2d(aes(colour = price_class)) + 
  theme_bw() +
  labs(title = "Density of Price Class")


#cowplot::plot_grid(d, c, nrow=1)

can see the spread of prices based on the area, can see that lower prices tend to located to the perimeter of New York, indicating that location towards centre of Manhattan is a large determinant of price.

Leaflet Map

 pal <- colorFactor(palette = c("orange", "white", "yellow", "red"), 
                    domain = prices_df$price_class)
Warning: Unknown or uninitialised column: `price_class`.
 leaflet(data = prices_new) %>% 
   addProviderTiles(providers$CartoDB.DarkMatterNoLabels) %>% 
  addCircleMarkers(~longitude, ~latitude, color = ~pal(price_class), weight = 1, 
                   radius=1, fillOpacity = 0.1, opacity = 0.1) %>%
     addLegend("bottomright", pal = pal, values = ~price_class,
     title = "Price Class",
     opacity = 1
   ) 

Leaflet map shows the contrast between extremities and city centre, evidently much higher prices in the city.

Summary

Prices are being impacted by: - location - type of accommodation

Model Building - Linear Regression

Approach: - manual - good way to become familiar with data - avoid over or under fitting the model

Goals of the model:

Distribution of Price

Dependent variable: Price

Important to investigate its distribution as it may require a transformation to create a better fit for the model

# distribution of price 
prices_df %>% 
  ggplot(aes(price)) +
  geom_histogram(bins = 30, aes(y = ..density..), fill = "red") + 
  geom_density(alpha = 0.2, fill = "red") +
  theme_bw() +
  labs(title = "Distribution of Price")

Price is heavily skewed to the right indicates a log transformation is needed to get a normal bell shaped distribution

# mean price 
mean_price <- prices_new %>% 
  summarise(m_price = mean(price))


# log bell shaped distribution 
prices_df %>% 
  ggplot(aes(price)) +
  geom_histogram(bins = 30, aes(y = ..density..), fill = "red") + 
  geom_density(alpha = 0.2, fill = "red") +
  geom_vline(data = mean_price, aes(xintercept =  m_price), size = 1, 
             linetype = 2) +
  theme_bw() +
  scale_x_log10() +
  labs(title = "Distribution of log(Price)")

Finalising Data for Model

Dropped variables:

  • ‘name’ - use dummy variables for important words and bigrams instead
  • ‘neighbourhood’ - use the categorical variable for five New York Boroughs
  • ‘price_class’ - high correlated with dependent variable price

Dummy variables created from text analysis:

  • ‘apartment’
  • ‘private’
  • ‘central park’
prices_reg_df <- prices_new %>% 
  # create dummy variables for chosen words that may impact price
  mutate(apartment_ad = if_else(str_detect(name, "[Aa]partment"), "YES", "NO"),
         private_ad = if_else(str_detect(name, "[Pp]rivate"), "YES", "NO"),
         central_park_ad = if_else(str_detect(name, "[Cc]entral [Pp]ark"), 
                                   "YES", "NO"),
         ) %>% 
  dplyr::select(-c(name, neighbourhood, price_class)) %>% 
  # log transform price
  mutate(log_price = log(price + 1)) %>% 
  # bring price to the front
  dplyr::select(log_price,price, everything()) %>% 
  # change all character variables to factor
  mutate(across(.cols = is.character, 
                .fns = as_factor)) %>% 
  dplyr::select(-price)
Warning: Use of bare predicate functions was deprecated in tidyselect 1.1.0.
Please use wrap predicates in `where()` instead.
# Was:
data %>% select(is.character)

# Now:
data %>% select(where(is.character))
  

Check alias() function to check for multicollinearity

alias(lm(log_price ~ ., data = prices_reg_df))
Model :
log_price ~ neighbourhood_group + latitude + longitude + room_type + 
    minimum_nights + number_of_reviews + reviews_per_month + 
    calculated_host_listings_count + availability_365 + last_review_month + 
    name_length + apartment_ad + private_ad + central_park_ad

Data is now ready to be used in regression analysis

Use ggpairs() to investigate which variables are highly correlated with price.

Data set is large so relationship analysis will be divided into numeric and non-numeric datatypes

variable_numeric <- prices_reg_df %>%
  select_if(is.numeric)

ggpairs(variable_numeric)

variable_nonnumeric <- prices_reg_df %>%
  select_if(function(x) !is.numeric(x))

variable_nonnumeric$log_price <- prices_reg_df$log_price

ggpairs(variable_nonnumeric)

Univariate Regression

Largest correlation with price: longitude - negatively correlated by 0.155 - statistically significant at the 0.001 level of significance.

The non-numeric ggpairs suggests that several variables will be able to explain price variance.

relationship between log(price) and longitude

prices_reg_df %>% 
  ggplot(aes(longitude, log_price)) + 
  geom_point() + 
  geom_smooth(method = "lm", se = FALSE, colour = "red") +
  theme_bw()

create model

model_1 <- lm(log_price ~ longitude, data = prices_reg_df)

autoplot(model_1)

Regression Diagnostics

graph 1 (population) - tell us most observations are independence, blue line declines at the end indicating other chunk of observations are not independently distributed. Need another variable.

graph 2 (distribution) - shows a fairly normal distribution, again, a bend at the end indicates model needs more to give a better distribution

graph 3 (homoskedasticity) - There is heteroskedasticity in the model indicated my curve on blue line

graph 4 (outliers) - There is a small number of outliers, and points are not highly leveraged.

model interpretation

summary(model_1)

Call:
lm(formula = log_price ~ longitude, data = prices_reg_df)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.7215 -0.4287 -0.0309  0.3813  4.6629 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept) -335.83166    5.02723  -66.80   <2e-16 ***
longitude     -4.60491    0.06798  -67.74   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.6256 on 38835 degrees of freedom
Multiple R-squared:  0.1057,    Adjusted R-squared:  0.1056 
F-statistic:  4589 on 1 and 38835 DF,  p-value: < 2.2e-16

coefficient:

  • An increase in longitude by one unit is associated with a change in price by 99.9% on average.

R-squared - longitude explains 10.6% of the variance of airbnb price.

Multivariate Regression

From the ggpairs plot and previous analysis, neighbourhood_group is suggests having an impact on airbnb prices, I will add this next.

Relationship between Price and Borough

prices_reg_df %>% 
  ggplot(aes(neighbourhood_group, log_price, fill = neighbourhood_group)) + 
  geom_boxplot(show.legend = FALSE) +
  theme_bw() + 
  labs(title = "Relationship between Price and Borough")

model_2 <- lm(log_price ~ longitude + neighbourhood_group, data = prices_reg_df)

autoplot(model_2)

regression diagnostics

graph 1 - independent population (achieved) graph 2 - still a skew in distribution graph 3 - Homoskdasticity (achieved) graph 4 - No highly leveraged points, but there are still (potentially) a few outliers

model interpretation

summary(model_2)

Call:
lm(formula = log_price ~ longitude + neighbourhood_group, data = prices_reg_df)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.5931 -0.4275 -0.0334  0.3609  4.6367 

Coefficients:
                                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)                      -3.283e+02  7.499e+00 -43.780  < 2e-16 ***
longitude                        -4.501e+00  1.014e-01 -44.391  < 2e-16 ***
neighbourhood_groupManhattan      2.790e-01  7.033e-03  39.667  < 2e-16 ***
neighbourhood_groupQueens         1.506e-01  1.294e-02  11.643  < 2e-16 ***
neighbourhood_groupStaten Island -9.425e-01  3.777e-02 -24.956  < 2e-16 ***
neighbourhood_groupBronx         -6.477e-02  2.201e-02  -2.942  0.00326 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.6038 on 38831 degrees of freedom
Multiple R-squared:  0.167, Adjusted R-squared:  0.1669 
F-statistic:  1557 on 5 and 38831 DF,  p-value: < 2.2e-16

coefficients:

  • all statistically significant at levels of significance - can reject the null hypothesis and say that coefficients are statistically different from zero.

  • An increase in Manhattan by one unit (zero to one) is associated with a change in price by (e^0.27-1) * 100 = 31%, holding all other factors constant

  • An increase in Staten Island by one unit (zero to one) is associated with a change in price by (e^-0.94 - 1) * 100 = -60.9%, holding all other factors constant

Anova function

anova(model_1, model_2)
Analysis of Variance Table

Model 1: log_price ~ longitude
Model 2: log_price ~ longitude + neighbourhood_group
  Res.Df   RSS Df Sum of Sq      F    Pr(>F)    
1  38835 15198                                  
2  38831 14156  4    1042.4 714.85 < 2.2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

anova function confirms the neighbourhood_group dummy variable was good to include in the model.

Model 3

before adding a new variable, must check the residuals of this model against the remaining variables in the dataset

check residuals

residuals <- prices_reg_df %>% 
  add_residuals(model_2) %>% 
  dplyr::select(-c(log_price, longitude, neighbourhood_group))

# seperate into numeric and non-numeric 

price_resid_numeric <- residuals %>%
  select_if(is.numeric)

price_resid_nonnumeric <- residuals %>%
  select_if(function(x) !is.numeric(x))

price_resid_nonnumeric$resid <- residuals$resid

ggpairs(price_resid_numeric)

ggpairs(price_resid_nonnumeric)

From numeric variables, availability_365 has the highest correlation with price a positive correlation of 0.131 (statistically significant).

However, the non-numeric variables indicate that room type may present a better explanation of price variance.

Comparison between impact of Room Type and Availability on Price

avail_plot <- price_resid_numeric %>% 
  ggplot(aes(availability_365, resid)) + 
  geom_point() +
  geom_smooth(method = "lm", se = FALSE, colour = "red") + 
  theme_bw() +
  labs(x = "Residuals", y = "Yearly Room Availability", 
       title = "Relationship Residuals and Availability") +
  theme(plot.title = element_text(size=10))

room_type_plot <- price_resid_nonnumeric %>% 
  ggplot(aes(room_type, resid, fill = room_type)) + 
  geom_boxplot(show.legend = FALSE) +
  theme_bw() + 
  labs(title = "Relationship Residuals and Room Type",
       x = "Room Type", y = "Residuals") +
  theme(plot.title = element_text(size=10))

cowplot::plot_grid(avail_plot, room_type_plot, nrow = 1) 
`geom_smooth()` using formula = 'y ~ x'

The comparison suggests room type should offer more explanation.

model_3 <- lm(log_price ~ longitude + neighbourhood_group + room_type, 
              data = prices_reg_df)
autoplot(model_3)

graph 1 - residuals are independent graph 2 - the residuals seem to be increasingly not distributed around zero with more skew at the beginning and end graph 3 - conditional variance of residuals is constant (homoskedasticity)

summary(model_3)

Call:
lm(formula = log_price ~ longitude + neighbourhood_group + room_type, 
    data = prices_reg_df)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.9468 -0.2982 -0.0401  0.2313  4.9777 

Coefficients:
                                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)                      -2.133e+02  5.899e+00 -36.160  < 2e-16 ***
longitude                        -2.942e+00  7.978e-02 -36.873  < 2e-16 ***
neighbourhood_groupManhattan      2.397e-01  5.495e-03  43.611  < 2e-16 ***
neighbourhood_groupQueens         1.183e-01  1.010e-02  11.709  < 2e-16 ***
neighbourhood_groupStaten Island -6.894e-01  2.951e-02 -23.358  < 2e-16 ***
neighbourhood_groupBronx         -5.006e-02  1.718e-02  -2.914  0.00357 ** 
room_typeEntire home/apt          7.443e-01  4.943e-03 150.572  < 2e-16 ***
room_typeShared room             -3.956e-01  1.659e-02 -23.841  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.4711 on 38829 degrees of freedom
Multiple R-squared:  0.493, Adjusted R-squared:  0.4929 
F-statistic:  5393 on 7 and 38829 DF,  p-value: < 2.2e-16

As anticipated, room_type greatly enhanced the explanation of the model. The R-squared suggests the model explains 49.3% of the variance in price.

The variable coefficients are all statistically significant therefore can be interpreted.

Model 4

Check residuals

residuals <- prices_reg_df %>% 
  add_residuals(model_3) %>% 
  dplyr::select(-c(log_price, longitude, neighbourhood_group, room_type))

# seperate into numeric and non-numeric 

price_resid_numeric <- residuals %>%
  select_if(is.numeric)

price_resid_nonnumeric <- residuals %>%
  select_if(function(x) !is.numeric(x))

price_resid_nonnumeric$resid <- residuals$resid

ggpairs(price_resid_numeric)

ggpairs(price_resid_nonnumeric)

As before, availability is displaying by far the strongest correlation, and there does not seem to be any stand out non-numeric variables. Therefore, room availability will be used in the next model.

model_4 <- lm(log_price ~ longitude + neighbourhood_group + room_type + availability_365,
              data = prices_reg_df)
autoplot(model_4)

graph 1 - model population is independently distributed graph 2 - still skew at both ends of graph, indicating graph is only somewhat normally distributed graph 3 - conditional variance of residuals is constant (homoskedastic)

summary(model_4)

Call:
lm(formula = log_price ~ longitude + neighbourhood_group + room_type + 
    availability_365, data = prices_reg_df)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.9243 -0.2919 -0.0406  0.2328  5.0684 

Coefficients:
                                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)                      -2.274e+02  5.815e+00 -39.107  < 2e-16 ***
longitude                        -3.131e+00  7.863e-02 -39.819  < 2e-16 ***
neighbourhood_groupManhattan      2.332e-01  5.408e-03  43.122  < 2e-16 ***
neighbourhood_groupQueens         1.041e-01  9.943e-03  10.466  < 2e-16 ***
neighbourhood_groupStaten Island -7.852e-01  2.915e-02 -26.939  < 2e-16 ***
neighbourhood_groupBronx         -7.984e-02  1.692e-02  -4.719 2.38e-06 ***
room_typeEntire home/apt          7.438e-01  4.861e-03 153.004  < 2e-16 ***
room_typeShared room             -4.276e-01  1.634e-02 -26.166  < 2e-16 ***
availability_365                  6.674e-04  1.840e-05  36.271  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.4633 on 38828 degrees of freedom
Multiple R-squared:  0.5096,    Adjusted R-squared:  0.5095 
F-statistic:  5043 on 8 and 38828 DF,  p-value: < 2.2e-16

R-squared has only increased marginally to 0.51 - model explains 51% of the variance in price

All variables in the model are statistically significant

Adding an interaction term

potential terms:

  • longitude:neighbourhood_group
  • longitude:room_type
  • longitude:availability_365
  • neighbourhood_group:room_type
  • neighbourhood_group:availability_365
  • room_type:availability_365

Through process of elimination, longitude:neighbourhood_group

model_5 <- lm(log_price ~ longitude + neighbourhood_group + room_type + 
                availability_365 + longitude:neighbourhood_group,
              data = prices_reg_df)

Plotting the interaction

price_resid <- prices_reg_df %>% 
  add_residuals(model_5) %>% 
  dplyr::select(-log_price)


coplot(resid ~ longitude | neighbourhood_group,
       panel = function(x, y, ...){
         points(x, y)
         abline(lm(y ~ x), col = "blue")
       },
       data = price_resid, rows = 1)

Model Diagnostics

autoplot(model_5)

graph 1 - population is independent graph 2 - model still not completely evenly distributed around 0 graph 3 - conditional variance of residuals is constant (homoskedasticity) graph 4 - there are still some outliers, but no highly leveraged points

Model Summary

summary(model_5)

Call:
lm(formula = log_price ~ longitude + neighbourhood_group + room_type + 
    availability_365 + longitude:neighbourhood_group, data = prices_reg_df)

Residuals:
    Min      1Q  Median      3Q     Max 
-4.9155 -0.2856 -0.0436  0.2300  5.1577 

Coefficients:
                                             Estimate Std. Error t value Pr(>|t|)    
(Intercept)                                -2.378e+02  1.031e+01 -23.065  < 2e-16 ***
longitude                                  -3.272e+00  1.394e-01 -23.468  < 2e-16 ***
neighbourhood_groupManhattan               -3.219e+02  1.557e+01 -20.677  < 2e-16 ***
neighbourhood_groupQueens                   1.761e+02  1.349e+01  13.053  < 2e-16 ***
neighbourhood_groupStaten Island            3.119e+02  5.582e+01   5.587 2.33e-08 ***
neighbourhood_groupBronx                    2.388e+02  3.601e+01   6.631 3.38e-11 ***
room_typeEntire home/apt                    7.255e-01  4.820e-03 150.506  < 2e-16 ***
room_typeShared room                       -4.263e-01  1.609e-02 -26.491  < 2e-16 ***
availability_365                            6.323e-04  1.815e-05  34.842  < 2e-16 ***
longitude:neighbourhood_groupManhattan     -4.354e+00  2.105e-01 -20.689  < 2e-16 ***
longitude:neighbourhood_groupQueens         2.382e+00  1.825e-01  13.053  < 2e-16 ***
longitude:neighbourhood_groupStaten Island  4.219e+00  7.533e-01   5.601 2.15e-08 ***
longitude:neighbourhood_groupBronx          3.233e+00  4.874e-01   6.633 3.32e-11 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.4562 on 38824 degrees of freedom
Multiple R-squared:  0.5246,    Adjusted R-squared:  0.5244 
F-statistic:  3570 on 12 and 38824 DF,  p-value: < 2.2e-16

Model Summary:

  • All explanatory variables are statistically significant

  • R-squared: model explains 52.5% of variance in price

    • model suffices as an ok explanation
  • Room_type had the greatest influence on the model

    • interpret the coefficient for room_type- entire/apt:
      • An increase in entire/apt by one unit (zero to one) is associated with a change in price by (e^0.73-1) * 100 = 107.5%, holding all other factors constant

model relative importance


calc.relimp(model_5, type = "lmg", rela = TRUE)
Response variable: log_price 
Total response variance: 0.4375758 
Analysis based on 38837 observations 

12 Regressors: 
Some regressors combined in groups: 
        Group  neighbourhood_group : neighbourhood_groupManhattan neighbourhood_groupQueens neighbourhood_groupStaten Island neighbourhood_groupBronx 
        Group  room_type : room_typeEntire home/apt room_typeShared room 
        Group  longitude:neighbourhood_group : longitude:neighbourhood_groupManhattan longitude:neighbourhood_groupQueens longitude:neighbourhood_groupStaten Island longitude:neighbourhood_groupBronx 

 Relative importance of 5 (groups of) regressors assessed: 
 neighbourhood_group room_type longitude:neighbourhood_group longitude availability_365 
 
Proportion of variance explained by model: 52.46%
Metrics are normalized to sum to 100% (rela=TRUE). 

Relative importance metrics: 

                                     lmg
neighbourhood_group           0.15650767
room_type                     0.66297003
longitude:neighbourhood_group 0.03891208
longitude                     0.11782702
availability_365              0.02378320

Average coefficients for different model sizes: 

                                                  1group       2groups       3groups       4groups       5groups
longitude                                  -4.6049149739 -4.1229356833 -4.051358e+00 -3.897941e+00 -3.271661e+00
neighbourhood_groupManhattan                0.3819453426  0.3211757943 -1.070997e+02 -2.511174e+02 -3.218665e+02
neighbourhood_groupQueens                  -0.2087349654 -0.0655638368  8.477795e+01  1.718999e+02  1.760652e+02
neighbourhood_groupStaten Island           -0.2501789720 -0.4947127336  9.315515e+01  2.287431e+02  3.118768e+02
neighbourhood_groupBronx                   -0.3667919672 -0.2372578195  1.284316e+02  2.514425e+02  2.387853e+02
room_typeEntire home/apt                    0.8196414599  0.7858184226  7.602355e-01  7.344142e-01  7.254613e-01
room_typeShared room                       -0.3741098014 -0.3871140671 -4.059261e-01 -4.118401e-01 -4.263369e-01
availability_365                            0.0003947914  0.0005442186  6.232164e-04  6.210777e-04  6.323326e-04
longitude:neighbourhood_groupManhattan               NaN           NaN -5.805751e+00 -5.096923e+00 -4.354219e+00
longitude:neighbourhood_groupQueens                  NaN           NaN  4.586125e+00  3.487592e+00  2.381855e+00
longitude:neighbourhood_groupStaten Island           NaN           NaN  5.069942e+00  4.647986e+00  4.219434e+00
longitude:neighbourhood_groupBronx                   NaN           NaN  6.959079e+00  5.105896e+00  3.232856e+00

Variables of relative importance:

Room type, perhaps unsurprisingly, is the most relevant variable for explaining variations in price. The least explanatory is availability_365

Conclusion and Recommendations

For Consumers:

For hosts: - How you describe your property doesn’t translate to being able to charge higher prices - will be able to charge a much higher price for property listed as whole apartment - property availability does not have much impact on price

Future analysis

More data on:

-yearly data - time-series analysis - identify cyclical trends

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGphbml0b3IpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KEdHYWxseSkKbGlicmFyeShnZ2ZvcnRpZnkpCmxpYnJhcnkobW9kZWxyKQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KHdvcmRjbG91ZCkKbGlicmFyeShsZWFmbGV0KQpsaWJyYXJ5KGdncmlkZ2VzKQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KHJlbGFpbXBvKQpsaWJyYXJ5KHRtKQpsaWJyYXJ5KFNub3diYWxsQykKbGlicmFyeSh3b3JkY2xvdWQpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpgYGAKCgojIEludHJvZHVjdGlvbgoKIyMgQWJvdXQgdGhlIERhdGEKClRoZSBkYXRhIGlzIGFib3V0IE5ldyBZb3JrIENpdHkgQWlyYm5iIGxpc3RpbmdzIGluIDIwMTkuIAoKVGhlIGRhdGEgaW5jbHVkZXMgaW5mb3JtYXRpb24gb24sIHByaWNlcywgTmV3IFlvcmsgbmVpZ2hib3VyaG9vZHMgYW5kIHJvb20gdHlwZXMsCnRvIG5hbWUgYSBmZXcuIEdlb3NwYXRpYWwgY29vcmRpbmF0ZXMgYXJlIGFsc28gcHJlc2VudCwgb2ZmZXJpbmcgdmFsdWFibGUgaW5zaWdodAppbnRvIEFpcmJuYiBsb2NhdGlvbnMuIAoKIyMgUHJvamVjdCBPYmplY3RpdmVzCgpQcmltYXJ5IGFpbToKLSBBbmFseXNlIHRoZSBkZXRlcm1pbmFudHMgb2YgYWlyYm5iIHByaWNlcwogIC0gT2ZmZXIgcmVjb21tZW5kYXRpb25zIGZvciBjdXN0b21lcnMgYm9va2luZyBhaXJibmJzCiAgLSBQcm92aWRlIGluc2lnaHRzIGZvciBob3N0cyAtIGFkb3B0IGFuIGFwcHJvcHJpYXRlIHByaWNpbmcgc3RyYXRlZ3kKICAKIyMgT3ZlcnZpZXcKCi0gRXRoaWNhbCBDb25zaWRlcmF0aW9ucwotIERhdGEgQ2xlYW5pbmcgYW5kIFdyYW5nbGluZwotIEV4cGxvcmF0b3J5IEFuYWx5c2lzCi0gVGV4dCBtaW5pbmcKLSBHZW8tc3BhdGlhbCBBbmFseXNpcwotIE1vZGVsIEJ1aWxkaW5nCiAgLSBVbml2YXJpYXRlIFJlZ3Jlc3Npb24KICAtIE11bHRpdmFyaWF0ZSBSZWdyZXNzaW9uCi0gQW5hbHlzaXMgQ29uY2x1c2lvbnMKCgpgYGB7cn0KcHJpY2VzIDwtIHJlYWRfY3N2KCJyYXdfZGF0YS9BQl9OWUNfMjAxOS5jc3YiKQpgYGAKCgojIEV0aGljYWwgQ29uc2lkZXJhdGlvbnMKClRoZSBkYXRhIGNvbnRhaW5zIGluZm9ybWF0aW9uIG9uIGhvc3QgbmFtZXMgYW5kIHVuaXF1ZSBJRHMuIFRvIGF2b2lkIGFueSBldGhpY2FsCmlzc3VlcyBJIGNob3NlIHRvIHJlbW92ZSB0aGVzZSB2YXJpYWJsZXMuIAoKLSBSZWR1Y2UgYmlhcwotIEZvbGxvdyBsYXcgCi0gZW5zdXJlIGNvbnN1bWVyIHRydXN0CgojIENsZWFuIERhdGEKCiMjIENoZWNrIE1pc3NpbmcgVmFsdWVzCgpgYGB7cn0KIyBjaGVjayBmb3IgbWlzc2luZyB2YWx1ZXMKcHJpY2VzICU+JSAKICBzdW1tYXJpc2UoCiAgICBhY3Jvc3MoCiAgICAgIC5jb2xzID0gZXZlcnl0aGluZygpLAogICAgICAuZm5zID0gfnN1bShpcy5uYSgueCkpCiAgICApCiAgKSAlPiUgCiAgZHBseXI6OnNlbGVjdChjKG5hbWUsIGhvc3RfbmFtZSwgbGFzdF9yZXZpZXcsIHJldmlld3NfcGVyX21vbnRoKSkKYGBgCk1hbnkgbWlzc2luZyB2YWx1ZXMgaW4gbGFzdF9yZXZpZXcgYW5kIHJldmlld3NfcGVyX21vbnRoICgxMDA1MiByb3dzKQoKLSBsYXN0X3JldmlldyB3aWxsIGJlIGRyb3BwZWQgYXMgaW5jb25zZXF1ZW50aWFsIHZhcmlhYmxlCi0gdmFsdWVzIGRyb3BwZWQgZnJvbSByZXZpZXdzX3Blcl9tb250aAogIC0gY29hbGVzY2luZyB3aXRoIG1lYW4gbWF5IHdhcnAgZGF0YSB0b28gbXVjaCAKCgpgYGB7cn0KcHJpY2VzX2RmIDwtIHByaWNlcyAlPiUgCiAgIyBkcm9wIGhvc3QgbmFtZXMgYW5kIGhvc3RfaWQgKGV0aGljYWwpCiAgZHBseXI6OnNlbGVjdCgtYyhob3N0X25hbWUsIGhvc3RfaWQsIGlkKSkgJT4lIAogICMgdGFrZSBvdXQgbW9udGggZnJvbSBsYXN0X3JldmlldyAKICBtdXRhdGUobGFzdF9yZXZpZXdfbW9udGggPSBtb250aChsYXN0X3JldmlldywgbGFiZWwgPSBUUlVFKSwKICAgICAgICAgIyB0YWtlIG91dCB5ZWFyIGZyb20gbGFzdF9yZXZpZXcKICAgICAgICAgbGFzdF9yZXZpZXdfeWVhciA9IHllYXIobGFzdF9yZXZpZXcpLAogICAgICAgICAjIHRha2UgbmFtZSBsZW5ndGgKICAgICAgICAgbmFtZV9sZW5ndGggPSBzdHJfbGVuZ3RoKG5hbWUpKSAlPiUgCiAgIyBkcm9wIE5BIHZhbHVlcwogIGRyb3BfbmEoKSAlPiUgCiAgZHBseXI6OnNlbGVjdCgtYyhsYXN0X3JldmlldywgbGFzdF9yZXZpZXdfeWVhcikpCgpgYGAKCgojIEV4cGxvcmF0b3J5IEFuYWx5c2lzCgojIyBUb3RhbCBOdW1iZXIgb2YgQm9va2luZ3MgcGVyIEJvcm91Z2gKCmBgYHtyfQojIG51bWJlciBvZiBib29raW5ncyBwZXIgYm9yb3VnaApwcmljZXNfZGYgJT4lIAogIGdyb3VwX2J5KG5laWdoYm91cmhvb2RfZ3JvdXApICU+JSAKICBzdW1tYXJpc2UobnVtX2Jvb2tpbmdzID0gbigpKSAlPiUgCiAgYXJyYW5nZShkZXNjKG51bV9ib29raW5ncykpICU+JSAKICBnZ3Bsb3QoYWVzKHJlb3JkZXIobmVpZ2hib3VyaG9vZF9ncm91cCwgbnVtX2Jvb2tpbmdzKSwgbnVtX2Jvb2tpbmdzLCAKICAgICAgICAgICAgIGZpbGwgPSBuZWlnaGJvdXJob29kX2dyb3VwKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX2xhYmVsKG1hcHBpbmcgPSBhZXMobGFiZWwgPSBudW1fYm9va2luZ3MpLCBzaXplID0gMywgCiAgICAgICAgICAgICBmaWxsID0gIiNGNUZGRkEiLCBmb250ZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIGxhYnMoeCA9ICJCb3JvdWdoIiwgeSA9ICJOdW1iZXIgb2YgQm9va2luZ3MiLCAKICAgICAgIHRpdGxlID0gIlRvdGFsIE51bWJlciBvZiBCb29raW5ncyBwZXIgQm9yb3VnaCIpCmBgYApNYW5oYXR0YW4gYW5kIEJyb29rbHluIGJvdGggYnkgZmFyIHRoZSBtb3N0IHBvcHVsYXIgYXJlYXMgZm9yIEFpcmJuYiBsaXN0aW5ncy4KClR3byBjZW50cmFsIEJvcm91Z2hzIHdoaWNoIG1heSBpbmRpY2F0ZSB0aGUgbWFpbiByZWFzb24gcGVvcGxlIGJvb2sgaXMgZm9yIApob2xpZGF5cyAvIFRvdXJpc20uIAoKCiMjIEF2ZXJhZ2UgUHJpY2UgcGVyIE5laWdoYm91cmhvb2QgR3JvdXAKCmBgYHtyfQoKIyBBdmVyYWdlIFByaWNlIHBlciBSb29tCnByaWNlX3Blcl9yb29tIDwtIHByaWNlc19kZiAlPiUgCiAgZ3JvdXBfYnkocm9vbV90eXBlKSAlPiUgCiAgc3VtbWFyaXNlKGF2Z19wcmljZSA9IG1lYW4ocHJpY2UpKSAlPiUgCiAgZ2dwbG90KGFlcyhyb29tX3R5cGUsIGF2Z19wcmljZSwgZmlsbCA9IHJvb21fdHlwZSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9sYWJlbChtYXBwaW5nID0gYWVzKGxhYmVsID0gcm91bmQoYXZnX3ByaWNlLCAyKSksIHNpemUgPSA2LCAKICAgICAgICAgICAgIGZpbGwgPSAiI0Y1RkZGQSIsIGZvbnRmYWNlID0gImJvbGQiLCAKICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjkpKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICBsYWJzKHggPSAiUm9vbSBUeXBlIiwgeSA9ICJBdmVyYWdlIFByaWNlICgkKSIsIHRpdGxlID0gIkF2ZXJhZ2UgUHJpY2UgYnkgUm9vbSBUeXBlIikKCgojIEF2ZXJhZ2UgcHJpY2UgcGVyIG5laWdoYm91cmhvb2QgZ3JvdXAKcHJpY2Vfcm9vbV9ib3JvdWdoIDwtIHByaWNlc19kZiAlPiUgCiAgZ3JvdXBfYnkobmVpZ2hib3VyaG9vZF9ncm91cCwgcm9vbV90eXBlKSAlPiUgCiAgc3VtbWFyaXNlKGF2Z19wcmljZSA9IG1lYW4ocHJpY2UpKSAlPiUgCiAgYXJyYW5nZShkZXNjKGF2Z19wcmljZSkpICU+JSAKICBnZ3Bsb3QoYWVzKHJlb3JkZXIobmVpZ2hib3VyaG9vZF9ncm91cCwgYXZnX3ByaWNlKSwgYXZnX3ByaWNlLCAKICAgICAgICAgICAgIGZpbGwgPSByb29tX3R5cGUpKSArCiAgZ2VvbV9jb2woKSArIAogIGdlb21fbGFiZWwobWFwcGluZyA9IGFlcyhsYWJlbCA9IHJvdW5kKGF2Z19wcmljZSwgMikpLCBzaXplID0gMi41LCAKICAgICAgICAgICAgIGZpbGwgPSAiI0Y1RkZGQSIsIGZvbnRmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSwgCiAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC45KSkgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgbGFicyh4ID0gIk5ldyBZb3JrIEJvcm91Z2hzIiwgeSA9ICJBdmVyYWdlIFByaWNlICgkKSIsIAogICAgICAgdGl0bGUgPSAiQXZlcmFnZSBQcmljZSBwZXIgQm9yb3VnaCBieSBSb29tIFR5cGUiKSArIAogIGZhY2V0X3dyYXAofnJvb21fdHlwZSkgKwogIGNvb3JkX2ZsaXAoKQoKY293cGxvdDo6cGxvdF9ncmlkKHByaWNlX3Blcl9yb29tLCBwcmljZV9yb29tX2Jvcm91Z2gsIG5yb3cgPSAyKQpgYGAKCgojIyBUaGUgMTAgbW9zdCBleHBlbnNpdmUgYW5kIGNoZWFwZXN0IE5ldyBZb3JrIERpc3RyaWN0cwoKYGBge3J9CiMgdG9wIDEwIG1vc3QgZXhwZW5zaXZlIGRpc3RyaWN0cyBvbiBhdmVyYWdlCmEgPC0gcHJpY2VzX2RmICU+JSAKICBncm91cF9ieShuZWlnaGJvdXJob29kKSAlPiUgCiAgc3VtbWFyaXNlKGF2Z19wcmljZSA9IG1lYW4ocHJpY2UpKSAlPiUgCiAgYXJyYW5nZShkZXNjKGF2Z19wcmljZSkpICU+JSAKICBzbGljZSgxOjEwKSAlPiUgCiAgZ2dwbG90KGFlcyhyZW9yZGVyKG5laWdoYm91cmhvb2QsIGF2Z19wcmljZSksIGF2Z19wcmljZSwgZmlsbCA9IG5laWdoYm91cmhvb2QpKSArIAogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX2xhYmVsKG1hcHBpbmcgPSBhZXMobGFiZWwgPSByb3VuZChhdmdfcHJpY2UsIDIpKSwgc2l6ZSA9IDMsIAogICAgICAgICAgICAgZmlsbCA9ICIjRjVGRkZBIiwgZm9udGZhY2UgPSAiYm9sZCIpICsKICAjIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDMwLCB2anVzdCA9IDAuOTUsIGhqdXN0ID0gMSkpICsKICBsYWJzKHggPSAiTmVpZ2hib3VyaG9vZCIsIHkgPSAiQXZlcmFnZSBQcmljZSAoJCkiLCAKICAgICAgIHRpdGxlID0gIlRoZSAxMCBNb3N0IEV4cGVuc2l2ZSBEaXN0cmljdHMgb24gQXZlcmFnZSIpCgojIHRvcCAxMCBsZWFzdCBleHBlbnNpdmUgZGlzdHJpY3RzIG9uIGF2ZXJhZ2UKYiA8LSBwcmljZXNfZGYgJT4lIAogIGdyb3VwX2J5KG5laWdoYm91cmhvb2QpICU+JSAKICBzdW1tYXJpc2UoYXZnX3ByaWNlID0gbWVhbihwcmljZSkpICU+JSAKICBhcnJhbmdlKGF2Z19wcmljZSkgJT4lIAogIHNsaWNlKDE6MTApICU+JSAKICBnZ3Bsb3QoYWVzKHJlb3JkZXIobmVpZ2hib3VyaG9vZCwgYXZnX3ByaWNlKSwgYXZnX3ByaWNlLCBmaWxsID0gbmVpZ2hib3VyaG9vZCkpICsgCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fbGFiZWwobWFwcGluZyA9IGFlcyhsYWJlbCA9IHJvdW5kKGF2Z19wcmljZSwgMikpLCBzaXplID0gMywgCiAgICAgICAgICAgICBmaWxsID0gIiNGNUZGRkEiLCBmb250ZmFjZSA9ICJib2xkIikgKwogICMgY29vcmRfZmxpcCgpICsKICB0aGVtZV9jbGFzc2ljKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzAsIHZqdXN0ID0gMC45NSwgaGp1c3QgPSAxKSkgKwogIGxhYnMoeCA9ICJOZWlnaGJvdXJob29kIiwgeSA9ICJBdmVyYWdlIFByaWNlICgkKSIsIAogICAgICAgdGl0bGUgPSAiVGhlIDEwIExlYXN0IEV4cGVuc2l2ZSBEaXN0cmljdHMgb24gQXZlcmFnZSIpCgpjb3dwbG90OjpwbG90X2dyaWQoYSwgYiwgbnJvdz0yKQpgYGAKClRoZSAxMCBtb3N0IGV4cGVuc2l2ZSBkaXN0cmljdHMgYXJlIGFsbCBsb2NhdGVkIGluIE1hbmhhdHRhbiBhcGFydCBmcm9tLCAKTmVwb25zaXQgKFF1ZWVucykgYW5kIFdpbGxvd0Jyb29rIChTdGF0ZW4gSXNsYW5kKQoKVGhlIG1ham9yaXR5IG9mIHRoZSAxMCBsZWFzdCBleHBlbnNpdmUgZGlzdHJpY3RzIHJlc2lkZSBpbiB0aGUgQnJvbngsIFN0YXRlIApJc2xhbmQgYW5kIFF1ZWVucwoKIyMgQXZlcmFnZSBSZXZpZXdzIGJ5IExhc3QgTW9udGggUmV2aWV3IFN1Ym1pdHRlZAoKYGBge3J9CiMgQXZlcmFnZSBSZXZpZXdzIHBlciBNb250aCBieSBMYXN0IE1vbnRoIHJldmlldyB3YXMgbGVmdApwcmljZXNfZGYgJT4lIAogIG11dGF0ZShsYXN0X3Jldmlld19tb250aCA9IGFzX2ZhY3RvcihsYXN0X3Jldmlld19tb250aCkpICU+JSAKICBncm91cF9ieShsYXN0X3Jldmlld19tb250aCkgJT4lIAogIHN1bW1hcmlzZShuID0gbWVhbihyZXZpZXdzX3Blcl9tb250aCwgbmEucm0gPSBGQUxTRSkpICU+JSAKICBnZ3Bsb3QoYWVzKGxhc3RfcmV2aWV3X21vbnRoLCBuLCBmaWxsID0gbGFzdF9yZXZpZXdfbW9udGgpKSArIAogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICB0aGVtZV9idygpICsKICBsYWJzKHggPSAiTGFzdCBSZXZpZXcgTW9udGgiLCB5ID0gIkF2ZXJhZ2UgUmV2aWV3cyBwZXIgTW9udGgiLCAKICAgICAgIHRpdGxlID0gIkF2ZXJhZ2UgUmV2aWV3cyBieSBMYXN0IE1vbnRoIFJldmlldyBTdWJtaXR0ZWQiKQpgYGAKClN1Z2dlc3RzIHRoYXQgbW9zdCBwZW9wbGUgYXJlIGxlYXZpbmcgcmV2aWV3cyBpbiB0aGUgc3VtbWVyLCBpbmRpY2F0aW5nIHNvbWUKc2Vhc29uYWxpdHkgdG8gQWlyYm5iIGJvb2tpbmcgaW4gTmV3IFlvcmsuIAoKCiMgUHJpY2UgRGVuc2l0eSBieSBBcmVhCgpgYGB7cn0KZ2dwbG90KHN1YnNldChwcmljZXNfZGYsIHByaWNlIDwgNTAwKSxhZXMoeCA9IHByaWNlKSkgKwogIGdlb21fZGVuc2l0eSgKICAgIG1hcHBpbmcgPSBhZXMoZmlsbCA9IG5laWdoYm91cmhvb2RfZ3JvdXApLCAKICAgIGJhbmR3aWR0aCA9IDEwMCwgCiAgICBhbHBoYSA9IDEsIHNpemUgPSAwLjUsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICB0aGVtZV9idygpICsKICAjIHNjYWxlX2ZpbGxfZWNvbm9taXN0KCkgKwogIGZhY2V0X3dyYXAofm5laWdoYm91cmhvb2RfZ3JvdXApICsKICBsYWJzKHggPSAiUHJpY2UiLCB5ID0gIkRlbnNpdHkiLCB0aXRsZSA9ICJQcmljZSBEZW5zaXR5IGJ5IEJvcm91Z2giKQpgYGAKClByaWNpbmcgZGVuc2l0eSBwbG90IHJldmVhbHMgdGhhdCBib3JvdWdocyB3aXRoIGZld2VyIGFtb3VudCBvZiBib29raW5ncyAoUXVlZW5zLApTdGF0ZW4gSXNsYW5kIGFuZCBCcm9ueCkgaGF2ZSBhIGhpZ2hlciBkZW5zaXR5IG9mIGxvd2VyIHByaWNlcyAKCk1vc3QgY29tbW9uIGFyZWFzIChNYWhhdHRhbiBhbmQgQnJvb2tseW4pIGhhdmUgYSB3aWRlciBkZW5zaXR5IHBsb3QgaW5kaWNhdGluZyAKdGhhdCBwcmljZXMgdmFyeSBtb3JlLiAKCgojIFRleHQgTWluaW5nCgpJIHdhbnQgdG8gZmluZCB0aGUgd29yZHMgdGhhdCBhcmUgYXNzb2NpYXRlZCB3aXRoIGRpZmZlcmVudCBwcmljZSByYW5nZXMuIAoKU28gSSBuZWVkIHRvIGNyZWF0ZSBuZXcgdmFyaWFibGVzIHdoaWNoIGNsYXNzaWZ5IHRoZSBwcmljZSByYW5nZSBvZiBlYWNoCmFpcmJuYgoKd2Ugd2lsbCBkZWZpbmUgcHJpY2UgcmFuZ2VzIGJhc2VkIGFyb3VuZCB0aGUgYXZlcmFnZSBwcmljZSBmb3IgdG90YWwgYm9va2luZ3MKCmBgYHtyfQpwcmljZXNfZGYgJT4lIAogIHN1bW1hcmlzZShtZWFuX3ByaWNlID0gbWVhbihwcmljZSkpCmBgYAptZWFuIHByaWNlIGlzICQxNDIgdGhlcmVmb3JlLCBsb3cgd2lsbCBiZSBsZXNzIHRoYW4gJDEwMCwgbWVkaXVtIHdpbGwgYmUgCmJldHdlZW4gJDEwMCAtICQyMDAsIGhpZ2ggd2lsbCBiZSAkMjAwIC0gJDMwMCBhbmQgdmVyeSBoaWdoIHdpbGwgYmUgZ3JlYXRlciB0aGFuCiQzMDAKCmBgYHtyfQpwcmljZXNfbmV3IDwtIHByaWNlc19kZiAlPiUgCiAgbXV0YXRlKHByaWNlX2NsYXNzID0gY2FzZV93aGVuKAogICAgcHJpY2UgPCAxMDAgfiAiTG93IiwgCiAgICBwcmljZSA8IDIwMCB+ICJNZWRpdW0iLCAKICAgIHByaWNlIDwgMzAwIH4gIkhpZ2giLAogICAgVFJVRSB+ICJWZXJ5IEhpZ2giCiAgKSkKICAKYGBgCgoKCiMjIEhpZ2gvVmVyeSBIaWdoIFByaWNlIFJhbmdlCgoKYGBge3J9CiMgd29yZHMgYXNzb2NpYXRlZCB3aXRoIGhpZ2ggcHJpY2VzCmhpZ2hfcHJpY2Vfd29yZHMgPC0gcHJpY2VzX25ldyAlPiUgCiAgIyBmaWx0ZXIgZm9yIEhpZ2ggYW5kIFZlcnkgSGlnaCBwcmljZSBjbGFzc2VzIAogIGZpbHRlcihwcmljZV9jbGFzcyAlaW4lIGMoIkhpZ2giLCAiVmVyeSBIaWdoIikpICU+JSAKICAjIHRha2UgaW5kaXZpZHVhbCB3b3JkcyBmcm9tIHRoZSBuYW1lIGNvbHVtbgogIHVubmVzdF90b2tlbnMod29yZCwgbmFtZSkgJT4lIAogICMgdGFrZSBvdXQgc3RvcF93b3JkcyAKICBhbnRpX2pvaW4oc3RvcF93b3JkcykgJT4lIAogICMgc2VsZWN0IHdvcmQgY29sdW1uCiAgZHBseXI6OnNlbGVjdCh3b3JkKQoKCnNvcnRlZF9ocF93b3JkcyA8LSBoaWdoX3ByaWNlX3dvcmRzICU+JSAKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkKCndvcmRjbG91ZCh3b3JkcyA9IHNvcnRlZF9ocF93b3JkcyR3b3JkLCBmcmVxID0gc29ydGVkX2hwX3dvcmRzJG4sIG1pbi5mcmVxID0gMSwgCiAgICAgICAgICBtYXgud29yZHM9NjAsIHJhbmRvbS5vcmRlcj1GQUxTRSwgcm90LnBlcj0wLjM1LCAKICAgICAgICAgIGNvbG9ycz1icmV3ZXIucGFsKDgsICJTcGVjdHJhbCIpKQoKYGBgCgoKV29yZHMgdGhhdCBzdGFuZCBvdXQgZnJvbSB3b3JkY2xvdWQgaW5jbHVkZTogYmVkcm9vbSwgYXBhcnRtZW50LCB2aWxsYWdlLCBsdXh1cnksIApsb2NhdGlvbiwgTWFuaGF0dGFuLCBzcGFjaW91cywgcGFyawotIHN0YXJ0IHRvIHVuZGVyc3RhbmQgd2hhdCBraW5kIG9mIEFpcmJuYnMgYXJlIGJlaW5nIGFkdmVydGlzZWQgZm9yIGhpZ2ggcHJpY2VzCgoKIyMgTG93IFByaWNlIFJhbmdlCgoKYGBge3J9CiMgd29yZHMgYXNzb2NpYXRlZCB3aXRoIGxvdyBwcmljZXMKbG93X3ByaWNlX3dvcmRzIDwtIHByaWNlc19uZXcgJT4lIAogIGZpbHRlcihwcmljZV9jbGFzcyAlaW4lICJMb3ciKSAlPiUgCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCBuYW1lKSAlPiUgCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JSAKICBkcGx5cjo6c2VsZWN0KHdvcmQpCgoKc29ydGVkX2xwX3dvcmRzIDwtIGxvd19wcmljZV93b3JkcyAlPiUgCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpCgp3b3JkY2xvdWQod29yZHMgPSBzb3J0ZWRfbHBfd29yZHMkd29yZCwgZnJlcSA9IHNvcnRlZF9scF93b3JkcyRuLCBtaW4uZnJlcSA9IDEsIG1heC53b3Jkcz02MCwgCiAgICAgICAgICByYW5kb20ub3JkZXI9RkFMU0UsIHJvdC5wZXI9MC4zNSwgY29sb3JzPWJyZXdlci5wYWwoOCwgIlNwZWN0cmFsIikpCmBgYAoKd2hlbiB3ZSBsb29rIGF0IHRoZSB3b3JkIGNsb3VkIG9mIHRoZSBsb3cgcHJpY2UgcmFuZ2UsIHdlIHNlZSBzb21lIHNpbWlsYXJpdGllcwp3aXRoIHRoZSBoaWdoIHByaWNlIHJhbmdlIGluZGljYXRpbmcgb3duZXJzIGFyZSB0cnlpbmcgdG8gc2VsbCB0aGUgcHJvcGVydHkgYXMKdXAgbWFya2V0LiAKCkJpZyBlbXBoYXNpcyBvbiBwcm9wZXJ0eSBiZWluZyAicHJpdmF0ZSIgd2hpY2ggaXMgbGlrZWx5IHRvIGJlIGEgYmlnIGNvbmNlcm4KZm9yIHBlb3BsZSB3aGVuIG5vdCBwYXlpbmcgdGhhdCBtdWNoLiBJbiBjb250cmFzdCwgcHJpdmFjeSBpcyBhIGdpdmVuIHdoZW4gcGF5aW5nCmZvciBoaWdoIGVuZCBhY2NvbW1vZGF0aW9uLiAKCgoKIyMgTWVkaXVtIFByaWNlIFJhbmdlCgpgYGB7cn0KIyB3b3JkcyBhc3NvY2lhdGVkIHdpdGggbWVkaXVtIHByaWNlIHJhbmdlIChhcm91bmQgdGhlIGF2ZXJhZ2UpCm1lZGl1bV9wcmljZV93b3JkcyA8LSBwcmljZXNfbmV3ICU+JSAKICBmaWx0ZXIocHJpY2VfY2xhc3MgJWluJSAiTWVkaXVtIikgJT4lIAogIHVubmVzdF90b2tlbnMod29yZCwgbmFtZSkgJT4lIAogIGFudGlfam9pbihzdG9wX3dvcmRzKSAlPiUgCiAgZHBseXI6OnNlbGVjdCh3b3JkKQoKCnNvcnRlZF9tcF93b3JkcyA8LSBtZWRpdW1fcHJpY2Vfd29yZHMgJT4lIAogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQoKd29yZGNsb3VkKHdvcmRzID0gc29ydGVkX21wX3dvcmRzJHdvcmQsIGZyZXEgPSBzb3J0ZWRfbXBfd29yZHMkbiwgbWluLmZyZXEgPSAxLCBtYXgud29yZHM9NjAsIAogICAgICAgICAgcmFuZG9tLm9yZGVyPUZBTFNFLCByb3QucGVyPTAuMzUsIGNvbG9ycz1icmV3ZXIucGFsKDgsICJTcGVjdHJhbCIpKQpgYGAKCk5vdCBhIHRyZW1lbmRvdXMgYW1vdW50IG9mIGRpZmZlcmVuY2UgaGVyZSwgcHJvYmFibHkgYXMgZXhwZWN0ZWQgaXQgdGFrZXMgYSBiYWxhbmNlCmJldHdlZW4gbG93IGFuZCBoaWdoIHByaWNlIHJhbmdlcyBoaWdobGlnaHRpbmcgcHJpdmFjeSBhcyBpbXBvcnRhbnQgYnV0IGFsc28gCm1vcmUgZW1waGFzaXMgb24gbG9jYXRpb24uIAoKCiMgTW9zdCBQb3B1bGFyIEJpZ3JhbXMgdXNlZCBpbiBBaXJibmIgTmFtZQoKQW5hbHlzaXMgb2YgdGhlIG1vc3QgcG9wdWxhciB3b3JkIGNvbWJpbmF0aW9ucwoKYGBge3J9CiMgT2J0YWluIHRoZSAyIHdvcmQgYmlncmFtcwpiaWdyYW1fbmFtZXMgPC0gcHJpY2VzX25ldyAlPiUgCiAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIG5hbWUsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKQoKIyBTZXBlcmF0ZSBpbnRvIHR3byB3b3JkcwpiaWdyYW1zX3NlcGFyYXRlZCA8LSBiaWdyYW1fbmFtZXMgJT4lIAogIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiKQoKIyBmaWx0ZXIgdGhlIHdvcmRzIGZvciBzdG9wIHdvcmRzCmJpZ3JhbXNfZmlsdGVyZWQgPC0gYmlncmFtc19zZXBhcmF0ZWQgJT4lIAogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JSAKICBmaWx0ZXIoIXdvcmQyICVpbiUgc3RvcF93b3JkcyR3b3JkKQoKIyBjb3VudCBhbmQgc29ydCB0aGUgbW9zdCBwb3B1bGFyIHdvcmRzCmJpZ3JhbXNfY291bnRzIDwtIGJpZ3JhbXNfZmlsdGVyZWQgJT4lIAogIGNvdW50KHdvcmQxLCB3b3JkMiwgc29ydCA9IFRSVUUpCgojIG1vc3QgcG9wdWxhciBiaWdyYW1zIGdyYXBoCmJpZ3JhbXNfY291bnRzICU+JSAKICAjIGZpbHRlciBvdXQgbnVtYmVycwogIGZpbHRlcighc3RyX2RldGVjdCh3b3JkMSwgIlswLTldIikpICU+JSAKICAjIHVuaXRlIGJvdGggd29yZCBjb2x1bW5zCiAgdW5pdGUoY29sID0gImJpZ3JhbXMiLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIsIHJlbW92ZSA9IFRSVUUpICU+JSAKICAjIGdldCB0b3AgMTAKICBzbGljZSgxOjEwKSAlPiUgCiAgIyB2aXN1YWxpc2UKICBnZ3Bsb3QoYWVzKHJlb3JkZXIoYmlncmFtcywgbiksIG4sIGZpbGwgPSBiaWdyYW1zKSkgKyAKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZV9idygpICsgCiAgZ2VvbV9sYWJlbChtYXBwaW5nID0gYWVzKGxhYmVsID0gbiksIHNpemUgPSAzLCAKICAgICAgICAgICAgIGZpbGwgPSAiI0Y1RkZGQSIsIGZvbnRmYWNlID0gImJvbGQiKSArCiAgbGFicyh5ID0gIk51bWJlciBvZiBCaWdyYW0gTWVudGlvbnMiLCB4ID0gIkJpZ3JhbSIsIAogICAgICAgdGl0bGUgPSAiTW9zdCBQb3B1bGFyIEJpZ3JhbXMgaW4gQWlyYm5iIE5hbWUiKQoKYGBgCgpIb3N0cyBlbXBoYXNpcyBvbiBsb2NhdGlvbiBpcyBhIGNvbW1vbiBzdHJhdGVneSB0byBlbnRpY2UgY29uc3VtZXJzCgpjb25zdW1lcnMgd2FudCB0byBiZSBjbG9zZXIgdG8gZmFtb3VzIGxhbmRtYXJrcy4gCgoKIyBHZW8tc3BhdGlhbCBBbmFseXNpcwoKCkNhbiB3ZSBzZWUgaWYgdGhlIGdlby1zcGF0aWFsIGJhY2tzIHVwIHRoZSB3b3JkIGNsb3VkIGFuZCBiaWdyYW0gYW5hbHlzaXMgb2YgCmVtcGhhc2lzIG9uIGxvY2F0aW9uIGZvciBtZWRpdW0gYW5kIGxvdyBwcmljZSByYW5nZXMKCiMjIERlbnNpdHkgb2YgTmV3IFlvcmsgQm9yb3VnaHMKCgpgYGB7cn0KIyBEZW5zaXR5IG9mIE5ldyBZb3JrIEJvcm91Z2hzCnByaWNlc19uZXcgJT4lIAogIGdncGxvdChhZXMobG9uZ2l0dWRlLCBsYXRpdHVkZSkpICsKICBnZW9tX2RlbnNpdHkyZChhZXMoY29sb3VyID0gbmVpZ2hib3VyaG9vZF9ncm91cCkpICsgCiAgdGhlbWVfYncoKSArCiAgbGFicyh0aXRsZSA9ICJEZW5zaXR5IG9mIEJvcm91Z2hzIikKYGBgCgojIyBEZW5zaXR5IG9mIFByaWNlIENsYXNzCgpgYGB7cn0KIyBEZW5zaXR5IG9mIFByaWNlIENsYXNzCnByaWNlc19uZXcgJT4lIAogIGdncGxvdChhZXMobG9uZ2l0dWRlLCBsYXRpdHVkZSkpICsKICBnZW9tX2RlbnNpdHkyZChhZXMoY29sb3VyID0gcHJpY2VfY2xhc3MpKSArIAogIHRoZW1lX2J3KCkgKwogIGxhYnModGl0bGUgPSAiRGVuc2l0eSBvZiBQcmljZSBDbGFzcyIpCgojY293cGxvdDo6cGxvdF9ncmlkKGQsIGMsIG5yb3c9MSkKCmBgYAoKCmNhbiBzZWUgdGhlIHNwcmVhZCBvZiBwcmljZXMgYmFzZWQgb24gdGhlIGFyZWEsIGNhbiBzZWUgdGhhdCBsb3dlciBwcmljZXMgCnRlbmQgdG8gbG9jYXRlZCB0byB0aGUgcGVyaW1ldGVyIG9mIE5ldyBZb3JrLCBpbmRpY2F0aW5nIHRoYXQgbG9jYXRpb24gdG93YXJkcwpjZW50cmUgb2YgTWFuaGF0dGFuIGlzIGEgbGFyZ2UgZGV0ZXJtaW5hbnQgb2YgcHJpY2UuCgoKIyMgTGVhZmxldCBNYXAKCmBgYHtyfQogcGFsIDwtIGNvbG9yRmFjdG9yKHBhbGV0dGUgPSBjKCJvcmFuZ2UiLCAid2hpdGUiLCAieWVsbG93IiwgInJlZCIpLCAKICAgICAgICAgICAgICAgICAgICBkb21haW4gPSBwcmljZXNfZGYkcHJpY2VfY2xhc3MpCgogbGVhZmxldChkYXRhID0gcHJpY2VzX25ldykgJT4lIAogICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLkRhcmtNYXR0ZXJOb0xhYmVscykgJT4lIAogIGFkZENpcmNsZU1hcmtlcnMofmxvbmdpdHVkZSwgfmxhdGl0dWRlLCBjb2xvciA9IH5wYWwocHJpY2VfY2xhc3MpLCB3ZWlnaHQgPSAxLCAKICAgICAgICAgICAgICAgICAgIHJhZGl1cz0xLCBmaWxsT3BhY2l0eSA9IDAuMSwgb3BhY2l0eSA9IDAuMSkgJT4lCiAgICAgYWRkTGVnZW5kKCJib3R0b21yaWdodCIsIHBhbCA9IHBhbCwgdmFsdWVzID0gfnByaWNlX2NsYXNzLAogICAgIHRpdGxlID0gIlByaWNlIENsYXNzIiwKICAgICBvcGFjaXR5ID0gMQogICApIApgYGAKCgpMZWFmbGV0IG1hcCBzaG93cyB0aGUgY29udHJhc3QgYmV0d2VlbiBleHRyZW1pdGllcyBhbmQgY2l0eSBjZW50cmUsIGV2aWRlbnRseQptdWNoIGhpZ2hlciBwcmljZXMgaW4gdGhlIGNpdHkuIAoKX19TdW1tYXJ5X18KClByaWNlcyBhcmUgYmVpbmcgaW1wYWN0ZWQgYnk6Ci0gbG9jYXRpb24KLSB0eXBlIG9mIGFjY29tbW9kYXRpb24gCgoKIyBNb2RlbCBCdWlsZGluZyAtIExpbmVhciBSZWdyZXNzaW9uCgpBcHByb2FjaDogCi0gbWFudWFsCi0gZ29vZCB3YXkgdG8gYmVjb21lIGZhbWlsaWFyIHdpdGggZGF0YQotIGF2b2lkIG92ZXIgb3IgdW5kZXIgZml0dGluZyB0aGUgbW9kZWwKCkdvYWxzIG9mIHRoZSBtb2RlbDogCgotIEEgd2VsbCBmaXQgbW9kZWwgdGhhdCByZWFzb25hYmx5IGV4cGxhaW5zIHZhcmlhbmNlIGluIHByaWNlCi0gc2F0aXNmaWVzIGFzc3VtcHRpb25zIG5lZWRlZCB0byB2YWxpZGF0ZSBtb2RlbAotIEJlIGFibGUgdG8gb3V0cHV0IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgY29lZmZpY2llbnRzIGZvciBjb25zdW1lci9ob3N0CnJlY29tbWVuZGF0aW9ucwoKCiMjIERpc3RyaWJ1dGlvbiBvZiBQcmljZQoKRGVwZW5kZW50IHZhcmlhYmxlOiBQcmljZQoKSW1wb3J0YW50IHRvIGludmVzdGlnYXRlIGl0cyBkaXN0cmlidXRpb24gYXMgaXQgbWF5IHJlcXVpcmUgYSB0cmFuc2Zvcm1hdGlvbiB0byAKY3JlYXRlIGEgYmV0dGVyIGZpdCBmb3IgdGhlIG1vZGVsCgoKYGBge3J9CiMgZGlzdHJpYnV0aW9uIG9mIHByaWNlIApwcmljZXNfZGYgJT4lIAogIGdncGxvdChhZXMocHJpY2UpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwLCBhZXMoeSA9IC4uZGVuc2l0eS4uKSwgZmlsbCA9ICJyZWQiKSArIAogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuMiwgZmlsbCA9ICJyZWQiKSArCiAgdGhlbWVfYncoKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgUHJpY2UiKQpgYGAKClByaWNlIGlzIGhlYXZpbHkgc2tld2VkIHRvIHRoZSByaWdodCBpbmRpY2F0ZXMgYSBsb2cgdHJhbnNmb3JtYXRpb24gaXMgbmVlZGVkCnRvIGdldCBhIG5vcm1hbCBiZWxsIHNoYXBlZCBkaXN0cmlidXRpb24KCmBgYHtyfQojIG1lYW4gcHJpY2UgCm1lYW5fcHJpY2UgPC0gcHJpY2VzX25ldyAlPiUgCiAgc3VtbWFyaXNlKG1fcHJpY2UgPSBtZWFuKHByaWNlKSkKCgojIGxvZyBiZWxsIHNoYXBlZCBkaXN0cmlidXRpb24gCnByaWNlc19kZiAlPiUgCiAgZ2dwbG90KGFlcyhwcmljZSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMzAsIGFlcyh5ID0gLi5kZW5zaXR5Li4pLCBmaWxsID0gInJlZCIpICsgCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC4yLCBmaWxsID0gInJlZCIpICsKICBnZW9tX3ZsaW5lKGRhdGEgPSBtZWFuX3ByaWNlLCBhZXMoeGludGVyY2VwdCA9ICBtX3ByaWNlKSwgc2l6ZSA9IDEsIAogICAgICAgICAgICAgbGluZXR5cGUgPSAyKSArCiAgdGhlbWVfYncoKSArCiAgc2NhbGVfeF9sb2cxMCgpICsKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBsb2coUHJpY2UpIikKYGBgCgoKCiMjIEZpbmFsaXNpbmcgRGF0YSBmb3IgTW9kZWwKCkRyb3BwZWQgdmFyaWFibGVzOgoKLSAnbmFtZScgLSB1c2UgZHVtbXkgdmFyaWFibGVzIGZvciBpbXBvcnRhbnQgd29yZHMgYW5kIGJpZ3JhbXMgaW5zdGVhZAotICduZWlnaGJvdXJob29kJyAtIHVzZSB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGUgZm9yIGZpdmUgTmV3IFlvcmsgQm9yb3VnaHMKLSAncHJpY2VfY2xhc3MnIC0gaGlnaCBjb3JyZWxhdGVkIHdpdGggZGVwZW5kZW50IHZhcmlhYmxlIHByaWNlCgpEdW1teSB2YXJpYWJsZXMgY3JlYXRlZCBmcm9tIHRleHQgYW5hbHlzaXM6IAoKLSAnYXBhcnRtZW50JwotICdwcml2YXRlJwotICdjZW50cmFsIHBhcmsnCgpgYGB7cn0KcHJpY2VzX3JlZ19kZiA8LSBwcmljZXNfbmV3ICU+JSAKICAjIGNyZWF0ZSBkdW1teSB2YXJpYWJsZXMgZm9yIGNob3NlbiB3b3JkcyB0aGF0IG1heSBpbXBhY3QgcHJpY2UKICBtdXRhdGUoYXBhcnRtZW50X2FkID0gaWZfZWxzZShzdHJfZGV0ZWN0KG5hbWUsICJbQWFdcGFydG1lbnQiKSwgIllFUyIsICJOTyIpLAogICAgICAgICBwcml2YXRlX2FkID0gaWZfZWxzZShzdHJfZGV0ZWN0KG5hbWUsICJbUHBdcml2YXRlIiksICJZRVMiLCAiTk8iKSwKICAgICAgICAgY2VudHJhbF9wYXJrX2FkID0gaWZfZWxzZShzdHJfZGV0ZWN0KG5hbWUsICJbQ2NdZW50cmFsIFtQcF1hcmsiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIllFUyIsICJOTyIpLAogICAgICAgICApICU+JSAKICBkcGx5cjo6c2VsZWN0KC1jKG5hbWUsIG5laWdoYm91cmhvb2QsIHByaWNlX2NsYXNzKSkgJT4lIAogICMgbG9nIHRyYW5zZm9ybSBwcmljZQogIG11dGF0ZShsb2dfcHJpY2UgPSBsb2cocHJpY2UgKyAxKSkgJT4lIAogICMgYnJpbmcgcHJpY2UgdG8gdGhlIGZyb250CiAgZHBseXI6OnNlbGVjdChsb2dfcHJpY2UscHJpY2UsIGV2ZXJ5dGhpbmcoKSkgJT4lIAogICMgY2hhbmdlIGFsbCBjaGFyYWN0ZXIgdmFyaWFibGVzIHRvIGZhY3RvcgogIG11dGF0ZShhY3Jvc3MoLmNvbHMgPSBpcy5jaGFyYWN0ZXIsIAogICAgICAgICAgICAgICAgLmZucyA9IGFzX2ZhY3RvcikpICU+JSAKICBkcGx5cjo6c2VsZWN0KC1wcmljZSkKICAKYGBgCgoKQ2hlY2sgYWxpYXMoKSBmdW5jdGlvbiB0byBjaGVjayBmb3IgbXVsdGljb2xsaW5lYXJpdHkgCgpgYGB7cn0KYWxpYXMobG0obG9nX3ByaWNlIH4gLiwgZGF0YSA9IHByaWNlc19yZWdfZGYpKQpgYGAKCkRhdGEgaXMgbm93IHJlYWR5IHRvIGJlIHVzZWQgaW4gcmVncmVzc2lvbiBhbmFseXNpcwoKCgpVc2UgZ2dwYWlycygpIHRvIGludmVzdGlnYXRlIHdoaWNoIHZhcmlhYmxlcyBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCBwcmljZS4KCkRhdGEgc2V0IGlzIGxhcmdlIHNvIHJlbGF0aW9uc2hpcCBhbmFseXNpcyB3aWxsIGJlIGRpdmlkZWQgaW50byBudW1lcmljIGFuZCAKbm9uLW51bWVyaWMgZGF0YXR5cGVzCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQp2YXJpYWJsZV9udW1lcmljIDwtIHByaWNlc19yZWdfZGYgJT4lCiAgc2VsZWN0X2lmKGlzLm51bWVyaWMpCgpnZ3BhaXJzKHZhcmlhYmxlX251bWVyaWMpCmBgYAoKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CnZhcmlhYmxlX25vbm51bWVyaWMgPC0gcHJpY2VzX3JlZ19kZiAlPiUKICBzZWxlY3RfaWYoZnVuY3Rpb24oeCkgIWlzLm51bWVyaWMoeCkpCgp2YXJpYWJsZV9ub25udW1lcmljJGxvZ19wcmljZSA8LSBwcmljZXNfcmVnX2RmJGxvZ19wcmljZQoKZ2dwYWlycyh2YXJpYWJsZV9ub25udW1lcmljKQpgYGAKCgojIyBVbml2YXJpYXRlIFJlZ3Jlc3Npb24KCkxhcmdlc3QgY29ycmVsYXRpb24gd2l0aCBwcmljZTogbG9uZ2l0dWRlCi0gbmVnYXRpdmVseSBjb3JyZWxhdGVkIGJ5IDAuMTU1Ci0gc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBhdCB0aGUgMC4wMDEgbGV2ZWwgb2Ygc2lnbmlmaWNhbmNlLiAKClRoZSBub24tbnVtZXJpYyBnZ3BhaXJzIHN1Z2dlc3RzIHRoYXQgc2V2ZXJhbCB2YXJpYWJsZXMgd2lsbCBiZSBhYmxlIHRvIGV4cGxhaW4gCnByaWNlIHZhcmlhbmNlLiAgCgojIyMgcmVsYXRpb25zaGlwIGJldHdlZW4gbG9nKHByaWNlKSBhbmQgbG9uZ2l0dWRlCgpgYGB7cn0KcHJpY2VzX3JlZ19kZiAlPiUgCiAgZ2dwbG90KGFlcyhsb25naXR1ZGUsIGxvZ19wcmljZSkpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3VyID0gInJlZCIpICsKICB0aGVtZV9idygpCmBgYApjcmVhdGUgbW9kZWwKCmBgYHtyfQptb2RlbF8xIDwtIGxtKGxvZ19wcmljZSB+IGxvbmdpdHVkZSwgZGF0YSA9IHByaWNlc19yZWdfZGYpCgphdXRvcGxvdChtb2RlbF8xKQpgYGAKCiMjIyBSZWdyZXNzaW9uIERpYWdub3N0aWNzIAoKZ3JhcGggMSAocG9wdWxhdGlvbikgLSB0ZWxsIHVzIG1vc3Qgb2JzZXJ2YXRpb25zIGFyZSBpbmRlcGVuZGVuY2UsIGJsdWUgbGluZQpkZWNsaW5lcyBhdCB0aGUgZW5kIGluZGljYXRpbmcgb3RoZXIgY2h1bmsgb2Ygb2JzZXJ2YXRpb25zIGFyZSBub3QgaW5kZXBlbmRlbnRseQpkaXN0cmlidXRlZC4gTmVlZCBhbm90aGVyIHZhcmlhYmxlLgoKZ3JhcGggMiAoZGlzdHJpYnV0aW9uKSAtIHNob3dzIGEgZmFpcmx5IG5vcm1hbCBkaXN0cmlidXRpb24sIGFnYWluLCBhIGJlbmQgYXQgdGhlCmVuZCBpbmRpY2F0ZXMgbW9kZWwgbmVlZHMgbW9yZSB0byBnaXZlIGEgYmV0dGVyIGRpc3RyaWJ1dGlvbgoKZ3JhcGggMyAoaG9tb3NrZWRhc3RpY2l0eSkgLSBUaGVyZSBpcyBoZXRlcm9za2VkYXN0aWNpdHkgaW4gdGhlIG1vZGVsIAppbmRpY2F0ZWQgbXkgY3VydmUgb24gYmx1ZSBsaW5lIAoKZ3JhcGggNCAob3V0bGllcnMpIC0gVGhlcmUgaXMgYSBzbWFsbCBudW1iZXIgb2Ygb3V0bGllcnMsIGFuZCBwb2ludHMgYXJlIG5vdApoaWdobHkgbGV2ZXJhZ2VkLiAKCgojIyMgbW9kZWwgaW50ZXJwcmV0YXRpb24KCmBgYHtyfQpzdW1tYXJ5KG1vZGVsXzEpCmBgYAoKX19jb2VmZmljaWVudDogX18KCi0gQW4gaW5jcmVhc2UgaW4gbG9uZ2l0dWRlIGJ5IG9uZSB1bml0IGlzIGFzc29jaWF0ZWQgd2l0aCBhIGNoYW5nZSBpbiBwcmljZSBieSAKX185OS45JV9fIG9uIGF2ZXJhZ2UuIAoKUi1zcXVhcmVkIC0gbG9uZ2l0dWRlIGV4cGxhaW5zIDEwLjYlIG9mIHRoZSB2YXJpYW5jZSBvZiBhaXJibmIgcHJpY2UuIAoKCgojIyBNdWx0aXZhcmlhdGUgUmVncmVzc2lvbgoKRnJvbSB0aGUgZ2dwYWlycyBwbG90IGFuZCBwcmV2aW91cyBhbmFseXNpcywgbmVpZ2hib3VyaG9vZF9ncm91cCBpcyBzdWdnZXN0cyAKaGF2aW5nIGFuIGltcGFjdCBvbiBhaXJibmIgcHJpY2VzLCBJIHdpbGwgYWRkIHRoaXMgbmV4dC4gCgoKCiMjIFJlbGF0aW9uc2hpcCBiZXR3ZWVuIFByaWNlIGFuZCBCb3JvdWdoCgpgYGB7cn0KcHJpY2VzX3JlZ19kZiAlPiUgCiAgZ2dwbG90KGFlcyhuZWlnaGJvdXJob29kX2dyb3VwLCBsb2dfcHJpY2UsIGZpbGwgPSBuZWlnaGJvdXJob29kX2dyb3VwKSkgKyAKICBnZW9tX2JveHBsb3Qoc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHRoZW1lX2J3KCkgKyAKICBsYWJzKHRpdGxlID0gIlJlbGF0aW9uc2hpcCBiZXR3ZWVuIFByaWNlIGFuZCBCb3JvdWdoIikKYGBgCgpgYGB7cn0KbW9kZWxfMiA8LSBsbShsb2dfcHJpY2UgfiBsb25naXR1ZGUgKyBuZWlnaGJvdXJob29kX2dyb3VwLCBkYXRhID0gcHJpY2VzX3JlZ19kZikKCmF1dG9wbG90KG1vZGVsXzIpCmBgYAoKIyMjIHJlZ3Jlc3Npb24gZGlhZ25vc3RpY3MKCmdyYXBoIDEgLSBpbmRlcGVuZGVudCBwb3B1bGF0aW9uIChhY2hpZXZlZCkKZ3JhcGggMiAtIHN0aWxsIGEgc2tldyBpbiBkaXN0cmlidXRpb24KZ3JhcGggMyAtIEhvbW9za2Rhc3RpY2l0eSAoYWNoaWV2ZWQpCmdyYXBoIDQgLSBObyBoaWdobHkgbGV2ZXJhZ2VkIHBvaW50cywgYnV0IHRoZXJlIGFyZSBzdGlsbCAocG90ZW50aWFsbHkpIGEgZmV3IApvdXRsaWVycwoKCiMjIyBtb2RlbCBpbnRlcnByZXRhdGlvbgoKYGBge3J9CnN1bW1hcnkobW9kZWxfMikKYGBgCgpfX2NvZWZmaWNpZW50czpfXyAKCi0gYWxsIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgYXQgbGV2ZWxzIG9mIHNpZ25pZmljYW5jZSAtIGNhbiByZWplY3QgdGhlIG51bGwKaHlwb3RoZXNpcyBhbmQgc2F5IHRoYXQgY29lZmZpY2llbnRzIGFyZSBzdGF0aXN0aWNhbGx5IGRpZmZlcmVudCBmcm9tIHplcm8uIAoKLSAgQW4gaW5jcmVhc2UgaW4gTWFuaGF0dGFuIGJ5IG9uZSB1bml0ICh6ZXJvIHRvIG9uZSkgaXMgYXNzb2NpYXRlZCB3aXRoIGEgCmNoYW5nZSBpbiBwcmljZSBieSAoZV4wLjI3LTEpICogMTAwID0gX18zMSVfXywgaG9sZGluZyBhbGwgb3RoZXIgZmFjdG9ycyAKY29uc3RhbnQgCgotIEFuIGluY3JlYXNlIGluIFN0YXRlbiBJc2xhbmQgYnkgb25lIHVuaXQgKHplcm8gdG8gb25lKSBpcyBhc3NvY2lhdGVkIHdpdGggYSAKY2hhbmdlIGluIHByaWNlIGJ5IChlXi0wLjk0IC0gMSkgKiAxMDAgPSBfXy02MC45JV9fLCBob2xkaW5nIGFsbCBvdGhlciBmYWN0b3JzIApjb25zdGFudAoKCiMjIyBBbm92YSBmdW5jdGlvbgoKYGBge3J9CmFub3ZhKG1vZGVsXzEsIG1vZGVsXzIpCmBgYAoKYW5vdmEgZnVuY3Rpb24gY29uZmlybXMgdGhlIG5laWdoYm91cmhvb2RfZ3JvdXAgZHVtbXkgdmFyaWFibGUgd2FzIGdvb2QgdG8gaW5jbHVkZQppbiB0aGUgbW9kZWwuIAoKCiMjIE1vZGVsIDMgCgpiZWZvcmUgYWRkaW5nIGEgbmV3IHZhcmlhYmxlLCBtdXN0IGNoZWNrIHRoZSByZXNpZHVhbHMgb2YgdGhpcyBtb2RlbCBhZ2FpbnN0IHRoZQpyZW1haW5pbmcgdmFyaWFibGVzIGluIHRoZSBkYXRhc2V0CgoKIyMjIGNoZWNrIHJlc2lkdWFscwoKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CnJlc2lkdWFscyA8LSBwcmljZXNfcmVnX2RmICU+JSAKICBhZGRfcmVzaWR1YWxzKG1vZGVsXzIpICU+JSAKICBkcGx5cjo6c2VsZWN0KC1jKGxvZ19wcmljZSwgbG9uZ2l0dWRlLCBuZWlnaGJvdXJob29kX2dyb3VwKSkKCiMgc2VwZXJhdGUgaW50byBudW1lcmljIGFuZCBub24tbnVtZXJpYyAKCnByaWNlX3Jlc2lkX251bWVyaWMgPC0gcmVzaWR1YWxzICU+JQogIHNlbGVjdF9pZihpcy5udW1lcmljKQoKcHJpY2VfcmVzaWRfbm9ubnVtZXJpYyA8LSByZXNpZHVhbHMgJT4lCiAgc2VsZWN0X2lmKGZ1bmN0aW9uKHgpICFpcy5udW1lcmljKHgpKQoKcHJpY2VfcmVzaWRfbm9ubnVtZXJpYyRyZXNpZCA8LSByZXNpZHVhbHMkcmVzaWQKCmdncGFpcnMocHJpY2VfcmVzaWRfbnVtZXJpYykKYGBgCgoKYGBge3IgbWVzc2FnZT1GQUxTRX0KZ2dwYWlycyhwcmljZV9yZXNpZF9ub25udW1lcmljKQpgYGAKCgpGcm9tIG51bWVyaWMgdmFyaWFibGVzLCBhdmFpbGFiaWxpdHlfMzY1IGhhcyB0aGUgaGlnaGVzdCBjb3JyZWxhdGlvbiB3aXRoIHByaWNlCmEgcG9zaXRpdmUgY29ycmVsYXRpb24gb2YgMC4xMzEgKHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQpLiAKCkhvd2V2ZXIsIHRoZSBub24tbnVtZXJpYyB2YXJpYWJsZXMgaW5kaWNhdGUgdGhhdCByb29tIHR5cGUgbWF5IHByZXNlbnQgYSBiZXR0ZXIKZXhwbGFuYXRpb24gb2YgcHJpY2UgdmFyaWFuY2UuIAoKCiMjIyBDb21wYXJpc29uIGJldHdlZW4gaW1wYWN0IG9mIFJvb20gVHlwZSBhbmQgQXZhaWxhYmlsaXR5IG9uIFByaWNlCgpgYGB7cn0KYXZhaWxfcGxvdCA8LSBwcmljZV9yZXNpZF9udW1lcmljICU+JSAKICBnZ3Bsb3QoYWVzKGF2YWlsYWJpbGl0eV8zNjUsIHJlc2lkKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGNvbG91ciA9ICJyZWQiKSArIAogIHRoZW1lX2J3KCkgKwogIGxhYnMoeCA9ICJSZXNpZHVhbHMiLCB5ID0gIlllYXJseSBSb29tIEF2YWlsYWJpbGl0eSIsIAogICAgICAgdGl0bGUgPSAiUmVsYXRpb25zaGlwIFJlc2lkdWFscyBhbmQgQXZhaWxhYmlsaXR5IikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCkpCgpyb29tX3R5cGVfcGxvdCA8LSBwcmljZV9yZXNpZF9ub25udW1lcmljICU+JSAKICBnZ3Bsb3QoYWVzKHJvb21fdHlwZSwgcmVzaWQsIGZpbGwgPSByb29tX3R5cGUpKSArIAogIGdlb21fYm94cGxvdChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgdGhlbWVfYncoKSArIAogIGxhYnModGl0bGUgPSAiUmVsYXRpb25zaGlwIFJlc2lkdWFscyBhbmQgUm9vbSBUeXBlIiwKICAgICAgIHggPSAiUm9vbSBUeXBlIiwgeSA9ICJSZXNpZHVhbHMiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSkKCmNvd3Bsb3Q6OnBsb3RfZ3JpZChhdmFpbF9wbG90LCByb29tX3R5cGVfcGxvdCwgbnJvdyA9IDEpIApgYGAKClRoZSBjb21wYXJpc29uIHN1Z2dlc3RzIHJvb20gdHlwZSBzaG91bGQgb2ZmZXIgbW9yZSBleHBsYW5hdGlvbi4gCgoKYGBge3J9Cm1vZGVsXzMgPC0gbG0obG9nX3ByaWNlIH4gbG9uZ2l0dWRlICsgbmVpZ2hib3VyaG9vZF9ncm91cCArIHJvb21fdHlwZSwgCiAgICAgICAgICAgICAgZGF0YSA9IHByaWNlc19yZWdfZGYpCmBgYAoKCmBgYHtyfQphdXRvcGxvdChtb2RlbF8zKQpgYGAKCgpncmFwaCAxIC0gcmVzaWR1YWxzIGFyZSBpbmRlcGVuZGVudApncmFwaCAyIC0gdGhlIHJlc2lkdWFscyBzZWVtIHRvIGJlIGluY3JlYXNpbmdseSBub3QgZGlzdHJpYnV0ZWQgYXJvdW5kIHplcm8Kd2l0aCBtb3JlIHNrZXcgYXQgdGhlIGJlZ2lubmluZyBhbmQgZW5kCmdyYXBoIDMgLSBjb25kaXRpb25hbCB2YXJpYW5jZSBvZiByZXNpZHVhbHMgaXMgY29uc3RhbnQgKGhvbW9za2VkYXN0aWNpdHkpCgoKYGBge3J9CnN1bW1hcnkobW9kZWxfMykKYGBgCgoKQXMgYW50aWNpcGF0ZWQsIHJvb21fdHlwZSBncmVhdGx5IGVuaGFuY2VkIHRoZSBleHBsYW5hdGlvbiBvZiB0aGUgbW9kZWwuIFRoZSAKUi1zcXVhcmVkIHN1Z2dlc3RzIHRoZSBtb2RlbCBleHBsYWlucyA0OS4zJSBvZiB0aGUgdmFyaWFuY2UgaW4gcHJpY2UuIAoKVGhlIHZhcmlhYmxlIGNvZWZmaWNpZW50cyBhcmUgYWxsIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgdGhlcmVmb3JlIGNhbiBiZQppbnRlcnByZXRlZC4gCgoKIyMgTW9kZWwgNAoKIyMjIENoZWNrIHJlc2lkdWFscwoKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CnJlc2lkdWFscyA8LSBwcmljZXNfcmVnX2RmICU+JSAKICBhZGRfcmVzaWR1YWxzKG1vZGVsXzMpICU+JSAKICBkcGx5cjo6c2VsZWN0KC1jKGxvZ19wcmljZSwgbG9uZ2l0dWRlLCBuZWlnaGJvdXJob29kX2dyb3VwLCByb29tX3R5cGUpKQoKIyBzZXBlcmF0ZSBpbnRvIG51bWVyaWMgYW5kIG5vbi1udW1lcmljIAoKcHJpY2VfcmVzaWRfbnVtZXJpYyA8LSByZXNpZHVhbHMgJT4lCiAgc2VsZWN0X2lmKGlzLm51bWVyaWMpCgpwcmljZV9yZXNpZF9ub25udW1lcmljIDwtIHJlc2lkdWFscyAlPiUKICBzZWxlY3RfaWYoZnVuY3Rpb24oeCkgIWlzLm51bWVyaWMoeCkpCgpwcmljZV9yZXNpZF9ub25udW1lcmljJHJlc2lkIDwtIHJlc2lkdWFscyRyZXNpZAoKZ2dwYWlycyhwcmljZV9yZXNpZF9udW1lcmljKQpnZ3BhaXJzKHByaWNlX3Jlc2lkX25vbm51bWVyaWMpCmBgYAoKQXMgYmVmb3JlLCBhdmFpbGFiaWxpdHkgaXMgZGlzcGxheWluZyBieSBmYXIgdGhlIHN0cm9uZ2VzdCBjb3JyZWxhdGlvbiwgYW5kIHRoZXJlCmRvZXMgbm90IHNlZW0gdG8gYmUgYW55IHN0YW5kIG91dCBub24tbnVtZXJpYyB2YXJpYWJsZXMuIFRoZXJlZm9yZSwgcm9vbSAKYXZhaWxhYmlsaXR5IHdpbGwgYmUgdXNlZCBpbiB0aGUgbmV4dCBtb2RlbC4gCgoKYGBge3J9Cm1vZGVsXzQgPC0gbG0obG9nX3ByaWNlIH4gbG9uZ2l0dWRlICsgbmVpZ2hib3VyaG9vZF9ncm91cCArIHJvb21fdHlwZSArIGF2YWlsYWJpbGl0eV8zNjUsCiAgICAgICAgICAgICAgZGF0YSA9IHByaWNlc19yZWdfZGYpCmBgYAoKCgpgYGB7cn0KYXV0b3Bsb3QobW9kZWxfNCkKYGBgCgpncmFwaCAxIC0gbW9kZWwgcG9wdWxhdGlvbiBpcyBpbmRlcGVuZGVudGx5IGRpc3RyaWJ1dGVkCmdyYXBoIDIgLSBzdGlsbCBza2V3IGF0IGJvdGggZW5kcyBvZiBncmFwaCwgaW5kaWNhdGluZyBncmFwaCBpcyBvbmx5IHNvbWV3aGF0Cm5vcm1hbGx5IGRpc3RyaWJ1dGVkCmdyYXBoIDMgLSBjb25kaXRpb25hbCB2YXJpYW5jZSBvZiByZXNpZHVhbHMgaXMgY29uc3RhbnQgKGhvbW9za2VkYXN0aWMpCgoKCmBgYHtyfQpzdW1tYXJ5KG1vZGVsXzQpCmBgYAoKClItc3F1YXJlZCBoYXMgb25seSBpbmNyZWFzZWQgbWFyZ2luYWxseSB0byAwLjUxIC0gbW9kZWwgZXhwbGFpbnMgNTElIG9mIHRoZSAKdmFyaWFuY2UgaW4gcHJpY2UKCkFsbCB2YXJpYWJsZXMgaW4gdGhlIG1vZGVsIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50CgoKCiMjIEFkZGluZyBhbiBpbnRlcmFjdGlvbiB0ZXJtCgpwb3RlbnRpYWwgdGVybXM6CgotIGxvbmdpdHVkZTpuZWlnaGJvdXJob29kX2dyb3VwCi0gbG9uZ2l0dWRlOnJvb21fdHlwZQotIGxvbmdpdHVkZTphdmFpbGFiaWxpdHlfMzY1Ci0gbmVpZ2hib3VyaG9vZF9ncm91cDpyb29tX3R5cGUKLSBuZWlnaGJvdXJob29kX2dyb3VwOmF2YWlsYWJpbGl0eV8zNjUKLSByb29tX3R5cGU6YXZhaWxhYmlsaXR5XzM2NQoKClRocm91Z2ggcHJvY2VzcyBvZiBlbGltaW5hdGlvbiwgbG9uZ2l0dWRlOm5laWdoYm91cmhvb2RfZ3JvdXAgCgpgYGB7cn0KbW9kZWxfNSA8LSBsbShsb2dfcHJpY2UgfiBsb25naXR1ZGUgKyBuZWlnaGJvdXJob29kX2dyb3VwICsgcm9vbV90eXBlICsgCiAgICAgICAgICAgICAgICBhdmFpbGFiaWxpdHlfMzY1ICsgbG9uZ2l0dWRlOm5laWdoYm91cmhvb2RfZ3JvdXAsCiAgICAgICAgICAgICAgZGF0YSA9IHByaWNlc19yZWdfZGYpCmBgYAoKCiMjIyBQbG90dGluZyB0aGUgaW50ZXJhY3Rpb24gCgpgYGB7cn0KcHJpY2VfcmVzaWQgPC0gcHJpY2VzX3JlZ19kZiAlPiUgCiAgYWRkX3Jlc2lkdWFscyhtb2RlbF81KSAlPiUgCiAgZHBseXI6OnNlbGVjdCgtbG9nX3ByaWNlKQoKCmNvcGxvdChyZXNpZCB+IGxvbmdpdHVkZSB8IG5laWdoYm91cmhvb2RfZ3JvdXAsCiAgICAgICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIC4uLil7CiAgICAgICAgIHBvaW50cyh4LCB5KQogICAgICAgICBhYmxpbmUobG0oeSB+IHgpLCBjb2wgPSAiYmx1ZSIpCiAgICAgICB9LAogICAgICAgZGF0YSA9IHByaWNlX3Jlc2lkLCByb3dzID0gMSkKYGBgCgoKIyMjIE1vZGVsIERpYWdub3N0aWNzCgpgYGB7cn0KYXV0b3Bsb3QobW9kZWxfNSkKYGBgCgpncmFwaCAxIC0gcG9wdWxhdGlvbiBpcyBpbmRlcGVuZGVudApncmFwaCAyIC0gbW9kZWwgc3RpbGwgbm90IGNvbXBsZXRlbHkgZXZlbmx5IGRpc3RyaWJ1dGVkIGFyb3VuZCAwCmdyYXBoIDMgLSBjb25kaXRpb25hbCB2YXJpYW5jZSBvZiByZXNpZHVhbHMgaXMgY29uc3RhbnQgKGhvbW9za2VkYXN0aWNpdHkpCmdyYXBoIDQgLSB0aGVyZSBhcmUgc3RpbGwgc29tZSBvdXRsaWVycywgYnV0IG5vIGhpZ2hseSBsZXZlcmFnZWQgcG9pbnRzCgoKIyMjIE1vZGVsIFN1bW1hcnkKCmBgYHtyfQpzdW1tYXJ5KG1vZGVsXzUpCmBgYApfX01vZGVsIFN1bW1hcnk6X18KCi0gQWxsIGV4cGxhbmF0b3J5IHZhcmlhYmxlcyBhcmUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudAoKLSBSLXNxdWFyZWQ6IG1vZGVsIGV4cGxhaW5zIDUyLjUlIG9mIHZhcmlhbmNlIGluIHByaWNlCiAgLSBtb2RlbCBzdWZmaWNlcyBhcyBhbiBvayBleHBsYW5hdGlvbgogIAotIFJvb21fdHlwZSBoYWQgdGhlIGdyZWF0ZXN0IGluZmx1ZW5jZSBvbiB0aGUgbW9kZWwKICAtIGludGVycHJldCB0aGUgY29lZmZpY2llbnQgZm9yIHJvb21fdHlwZS0gZW50aXJlL2FwdDoKICAgIC0gQW4gaW5jcmVhc2UgaW4gZW50aXJlL2FwdCBieSBvbmUgdW5pdCAoemVybyB0byBvbmUpIGlzIGFzc29jaWF0ZWQgd2l0aCBhIApjaGFuZ2UgaW4gcHJpY2UgYnkgKGVeMC43My0xKSAqIDEwMCA9IF9fMTA3LjUlX18sIGhvbGRpbmcgYWxsIG90aGVyIGZhY3RvcnMgCmNvbnN0YW50CgoKIyMjIG1vZGVsIHJlbGF0aXZlIGltcG9ydGFuY2UgCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQoKY2FsYy5yZWxpbXAobW9kZWxfNSwgdHlwZSA9ICJsbWciLCByZWxhID0gVFJVRSkKYGBgCgpfX1ZhcmlhYmxlcyBvZiByZWxhdGl2ZSBpbXBvcnRhbmNlOl9fIAoKUm9vbSB0eXBlLCBwZXJoYXBzIHVuc3VycHJpc2luZ2x5LCBpcyB0aGUgbW9zdCByZWxldmFudCB2YXJpYWJsZSBmb3IgZXhwbGFpbmluZwp2YXJpYXRpb25zIGluIHByaWNlLiAKVGhlIGxlYXN0IGV4cGxhbmF0b3J5IGlzIGF2YWlsYWJpbGl0eV8zNjUKCgojIENvbmNsdXNpb24gYW5kIFJlY29tbWVuZGF0aW9ucwoKX19Gb3IgQ29uc3VtZXJzOl9fIAoKLSBUaGUgcm9vbSB0eXBlIGFkdmVydGlzZWQgaXMgdmVyeSBpbXBvcnRhbnQgdG8gZGV0ZXJtaW5pbmcgdGhlIHByaWNlIHlvdSBwYXkKLSBsb2NhdGlvbiBhbmFseXNpcyBzdWdnZXN0cyBjaXR5IGNlbnRyZSBhcmVhcyBkZW1hbmQgbXVjaCBoaWdoZXIgcHJpY2VzIHRoYW4gCm91dHNraXJ0IGFyZWFzIHN1Y2ggYXMgU3RhdGVuIElzbGFuZAoKX19Gb3IgaG9zdHM6X18gCi0gSG93IHlvdSBkZXNjcmliZSB5b3VyIHByb3BlcnR5IGRvZXNuJ3QgdHJhbnNsYXRlIHRvIGJlaW5nIGFibGUgdG8gY2hhcmdlCmhpZ2hlciBwcmljZXMKLSB3aWxsIGJlIGFibGUgdG8gY2hhcmdlIGEgbXVjaCBoaWdoZXIgcHJpY2UgZm9yIHByb3BlcnR5IGxpc3RlZCBhcyB3aG9sZSBhcGFydG1lbnQKLSBwcm9wZXJ0eSBhdmFpbGFiaWxpdHkgZG9lcyBub3QgaGF2ZSBtdWNoIGltcGFjdCBvbiBwcmljZQoKCiMgRnV0dXJlIGFuYWx5c2lzCgpNb3JlIGRhdGEgb246CgoteWVhcmx5IGRhdGEKICAtIHRpbWUtc2VyaWVzIGFuYWx5c2lzCiAgICAtIGlkZW50aWZ5IGN5Y2xpY2FsIHRyZW5kcw==